sql >> Base de Datos >  >> NoSQL >> MongoDB

Spring Data MongoDB:índices, anotaciones y convertidores

1. Resumen

En este tutorial, exploraremos algunas de las funciones principales de Spring Data MongoDB:indexación, anotaciones comunes y convertidores.

2. Índices

2.1. @Indexado

Esta anotación marca el campo como indexado en MongoDB:

@QueryEntity
@Document
public class User {
    @Indexed
    private String name;
    
    ... 
}

Ahora que el nombre el campo está indexado:echemos un vistazo a los índices en el shell de MongoDB:

db.user.getIndexes();

Esto es lo que obtenemos:

[
    {
        "v" : 1,
        "key" : {
             "_id" : 1
         },
        "name" : "_id_",
        "ns" : "test.user"
    }
]

Puede que nos sorprenda que no haya ninguna señal del nombre campo en cualquier lugar!

Esto se debe a que, a partir de Spring Data MongoDB 3.0, la creación automática de índices está desactivada de forma predeterminada .

Sin embargo, podemos cambiar ese comportamiento anulando explícitamente autoIndexCreation() método en nuestro MongoConfig :

public class MongoConfig extends AbstractMongoClientConfiguration {

    // rest of the config goes here

    @Override
    protected boolean autoIndexCreation() {
        return true;
    }
}

Revisemos nuevamente los índices en el shell de MongoDB:

[
    {
        "v" : 1,
        "key" : {
             "_id" : 1
         },
        "name" : "_id_",
        "ns" : "test.user"
    },
    {
         "v" : 1,
         "key" : {
             "name" : 1
          },
          "name" : "name",
          "ns" : "test.user"
     }
]

Como podemos ver, esta vez tenemos dos índices, uno de ellos es _id – que se creó de forma predeterminada debido a @Id anotación y la segunda es nuestro nombre campo.

Alternativamente, si usamos Spring Boot, podríamos configurar spring.data.mongodb.auto-index-creation propiedad a verdadero .

2.2. Crear un índice programáticamente

También podemos crear un índice programáticamente:

mongoOps.indexOps(User.class).
  ensureIndex(new Index().on("name", Direction.ASC));

Ahora hemos creado un índice para el campo nombre y el resultado será el mismo que en el apartado anterior.

2.3. Índices compuestos

MongoDB admite índices compuestos, donde una estructura de índice único contiene referencias a varios campos.

Veamos un ejemplo rápido usando índices compuestos:

@QueryEntity
@Document
@CompoundIndexes({
    @CompoundIndex(name = "email_age", def = "{'email.id' : 1, 'age': 1}")
})
public class User {
    //
}

Creamos un índice compuesto con el email y edad los campos. Veamos ahora los índices reales:

{
    "v" : 1,
    "key" : {
        "email.id" : 1,
        "age" : 1
    },
    "name" : "email_age",
    "ns" : "test.user"
}

Tenga en cuenta que un DBRef el campo no se puede marcar con @Index – ese campo solo puede ser parte de un índice compuesto.

3. Anotaciones comunes

3.1. @Transitorio

Como era de esperar, esta simple anotación excluye que el campo se mantenga en la base de datos:

public class User {
    
    @Transient
    private Integer yearOfBirth;
    // standard getter and setter

}

Insertemos usuario con el campo de configuración yearOfBirth :

User user = new User();
user.setName("Alex");
user.setYearOfBirth(1985);
mongoTemplate.insert(user);

Ahora, si miramos el estado de la base de datos, vemos que el añoDeNacimiento archivado no se guardó:

{
    "_id" : ObjectId("55d8b30f758fd3c9f374499b"),
    "name" : "Alex",
    "age" : null
}

Entonces, si consultamos y comprobamos:

mongoTemplate.findOne(Query.query(Criteria.where("name").is("Alex")), User.class).getYearOfBirth()

El resultado será nulo .

3.2. @Campo

@Campo indica la clave que se utilizará para el campo en el documento JSON:

@Field("email")
private EmailAddress emailAddress;

Ahora dirección de correo electrónico se guardará en la base de datos usando la clave email:

User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("[email protected]");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);

Y el estado de la base de datos:

{
    "_id" : ObjectId("55d076d80bad441ed114419d"),
    "name" : "Brendan",
    "age" : null,
    "email" : {
        "value" : "[email protected]"
    }
}

3.3. @PersistentConstructor y @Valor

@PersistentConstructor marca un constructor, incluso uno que está protegido por paquetes, para que sea el constructor principal utilizado por la lógica de persistencia. Los argumentos del constructor se asignan por nombre a los valores clave en el DBObject recuperado. .

Veamos este constructor para nuestro Usuario clase:

@PersistenceConstructor
public User(String name, @Value("#root.age ?: 0") Integer age, EmailAddress emailAddress) {
    this.name =  name;
    this.age = age;
    this.emailAddress =  emailAddress;
}

Observe el uso del estándar Spring @Value anotación aquí. Es con la ayuda de esta anotación que podemos usar Spring Expressions para transformar el valor de una clave recuperada de la base de datos antes de que se use para construir un objeto de dominio. Esa es una característica muy poderosa y muy útil aquí.

En nuestro ejemplo, si edad no está establecido, se establecerá en 0 por defecto.

Ahora veamos cómo funciona:

User user = new User();
user.setName("Alex");
mongoTemplate.insert(user);

Nuestra base de datos buscará:

{
    "_id" : ObjectId("55d074ca0bad45f744a71318"),
    "name" : "Alex",
    "age" : null
}

Así que la edad el campo es null , pero cuando consultamos el documento y recuperamos age :

mongoTemplate.findOne(Query.query(Criteria.where("name").is("Alex")), User.class).getAge();

El resultado será 0.

4. Convertidores

Ahora echemos un vistazo a otra característica muy útil en Spring Data MongoDB:los convertidores, y específicamente en el MongoConverter .

Esto se usa para manejar el mapeo de todos los tipos de Java a DBObjects al almacenar y consultar estos objetos.

Tenemos dos opciones:podemos trabajar con MappingMongoConverter: o SimpleMongoConverter en versiones anteriores (esto quedó en desuso en Spring Data MongoDB M3 y su funcionalidad se ha movido a MappingMongoConverter ).

O podemos escribir nuestro propio convertidor personalizado. Para hacer eso, necesitaríamos implementar el Convertidor interfaz y registre la implementación en MongoConfig.

Veamos un ejemplo rápido . Como hemos visto en algunos de los resultados JSON aquí, todos los objetos guardados en una base de datos tienen el campo _class que se guarda automáticamente. Sin embargo, si nos gustaría omitir ese campo en particular durante la persistencia, podemos hacerlo usando un MappingMongoConverter .

Primero:aquí está la implementación del convertidor personalizado:

@Component
public class UserWriterConverter implements Converter<User, DBObject> {
    @Override
    public DBObject convert(User user) {
        DBObject dbObject = new BasicDBObject();
        dbObject.put("name", user.getName());
        dbObject.put("age", user.getAge());
        if (user.getEmailAddress() != null) {
            DBObject emailDbObject = new BasicDBObject();
            emailDbObject.put("value", user.getEmailAddress().getValue());
            dbObject.put("email", emailDbObject);
        }
        dbObject.removeField("_class");
        return dbObject;
    }
}

Observe cómo podemos alcanzar fácilmente el objetivo de no persistir _class eliminando específicamente el campo directamente aquí.

Ahora necesitamos registrar el convertidor personalizado:

private List<Converter<?,?>> converters = new ArrayList<Converter<?,?>>();

@Override
public MongoCustomConversions customConversions() {
    converters.add(new UserWriterConverter());
    return new MongoCustomConversions(converters);
}

Por supuesto, también podemos lograr el mismo resultado con la configuración XML, si necesitamos:

<bean id="mongoTemplate" 
  class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongo" ref="mongo"/>
    <constructor-arg ref="mongoConverter" />
    <constructor-arg name="databaseName" value="test"/>
</bean>

<mongo:mapping-converter id="mongoConverter" base-package="org.baeldung.converter">
    <mongo:custom-converters base-package="com.baeldung.converter" />
</mongo:mapping-converter>

Ahora, cuando guardamos un nuevo usuario:

User user = new User();
user.setName("Chris");
mongoOps.insert(user);

El documento resultante en la base de datos ya no contiene la información de la clase:

{
    "_id" : ObjectId("55cf09790bad4394db84b853"),
    "name" : "Chris",
    "age" : null
}