Translate

Mostrando entradas con la etiqueta MongoDB. Mostrar todas las entradas
Mostrando entradas con la etiqueta MongoDB. Mostrar todas las entradas

domingo, 23 de marzo de 2014

MongoDB Spring Security Code Example

Titulo: Ejemplo práctico de Spring Security con MongoDB con código fuente.

I have decided to change the main language of my blog in order to reach and share everything with more people.

In this post you can see the next topics

- SpringData With MongoDb Integration
- Spring MVC to manage navigation
- Spring Security with Custom user detais service to assign roles and web application authentication.




Everything is easy and separated in different files to be more understandable and you have all the code in github.

This example is based on the book Spring Security 3.x Cookbook  that cover a lot of spring security integrations with different technologies:
  1. ORM (Hibernate) and NoSQL Databases (MongoDB) 
  2. JSF
  3. JSP
  4. Struts 2
  5. GWT
  6. Grails
  7. Vaadin
  8. Wicked
  9. Facebook, Twitter
  10. WebServices: RESTFull, SOAP
  11. Captcha Integration
  12. and more all with code samples
After this recommendation, lets go to show the example, all the code you can find it in my GitHub repository.

Please download the code as ZIP or fork the repository to follow the example.

To run the example you have the following requirements:
1- Eclipse Kepler to import the project from github or zip file.
2 - Document data base MongoDB installed in the same machine as your application server.
3 - Application Server Apache Tomcat 7
4 - Configure your application path context to "/" in ordet to Spring Security secure your URL.



Import the project into eclipse like a Maven project and deploy it in a Tomcat server sever with the above path configuration.

Populate you MongoDB database with users data, you can do it easily executing this test file in the application.UserPopulateCollection.java

Remember to uncomment the //@Test line.



or insert in the Mongo Database the next data into User Collection




The application  is a 4 screens application, we have a Login page where you can login like:

1 - Admin User (Role 1 - ROLE_ADMIN): You can access to User and Campaign pages but not create new campaigns.
2 - Campaign User (Role 2 - ROLE_CAMPAIGN): You only can access to Campaign page and create new one, but you can not access to users pages.


You can access the application from the following url using any of these users

http://localhost:8080/login

User Admin
user: user1@antuansoft.es
pwd: 123

User Campaign
user: user2@antuansoft.es
pwd 123


Menu page with the different application´s options Users and Campaings.



Users page where you have all the info about your application users.




Campaing page where you can access to all information of your campaings and can add and delete it



I think it's time we have a look at the project configuration files,

A - pom.xml

In this file you can see al the dependencies that we need, all of this related with Spring and MongoDB.

B - web.xml

Here we have the main project configuration, where the application server starts reading de config files.

Open the file in github or in your eclipse project and you can see:
1- The Spring Security filter configuration to secure all the URLs
2 - Spring MVC servlet dispatcher to manager all the URL requests.
3 - Log4J configuration
4 - Spring modules (Spring Data, Spring Security, Spring MVC) configuration files

 <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>
      /WEB-INF/mvc-config.xml
      /WEB-INF/security-config.xml       
      /WEB-INF/mongo-config.xml
     </param-value>
 </context-param>
        

C - mvc-config.xml

In this file there are the basic MVC configuration and packages to scan.

1 - Set the Spring context to  manage requests with the Spring MVC Controllers with the tag        <mvc:annotation-driven />
2 - Set the packages to scan Spring beans annotated with @RequestMapping @Component, etc...

  <context:component-scan base-package="com.antuansoft.spring.mvc">  
  <context:component-scan base-package="com.antuansoft.spring.security">  

3 - Define the views configuration location ant type, in this case with jsp, using JSTL.

D - mongo-config.xml

Here we have the MongDB configuration

1 - We define the database parameters, host and port and the database name in the mongoDbFactory. This is the main configuration to connect with database.

2- Define the MongoRepositories, this is where Spring Data is going to scan to look for Mongo DAOS (Mongo Data Acces Objects). In this DAOS is where all the database operations (save, delete, etc.) and the domain objects (User, and Campaign) are defined.

<mongo:repositories base-package="com.antuansoft.mongodb.repositories" />

E - security-config.xml

The security configuration of the application is defined here.

1 - Enable the @PreAuthorize annotation that will be user in MainController class

2 - Secure the application´s  urls. Due to this config is important to change the application path context to "/" like we said above.
   - /login, /logout and /accessdenied urls are permited to authenticated and not  authenticated users.
   - to access to /menu you have to be authenticated with ROLE_ADMIN or ROLE_CAMPAIGN
   - to access to /listUsers you have to be authenticated with ROLE_ADMIN
   - to access to /listCampaigns you have to be authenticated with ROLE_ADMIN or ROLE_CAMPAIGN
   - Finally the spring security login form is too easy to understand.


3 - The most important part is the definition of a custom class (mongoUserDetailsService) to authenticate users application access. An the password encoding, here we use plain text but  i recommend  to use MD5.

<authentication-manager alias="authenticationManager">
 <authentication-provider user-service-ref="mongoUserDetailsService">
   <password-encoder hash="plaintext" />
  </authentication-provider>
 </authentication-manager>

After configuration files we are going to review the specific Java files to understand the Spring Data, Spring Mvc and Spring Security features.

Spring Security:

Open the file MongoUserDetailsService.java

This is the file we have defined in security-config.xml as the authentication provider, this class have to implement the SpringFramework interface UserDetailsService and create the method UserDetailsService.

This method is used to check if the user data send in login form is valid against your database info, this method have to return a org.springframework.security.core.userdetails.User class with the data of the user found in your database.

You have to query your user collection and get the user info, we use the method getUserDetail that call to userRepositoryDao.findByUsername(username);

After getting  user info and based on this, the user role is defined in getAuthorities method.and pass it to UserDetails object.

getUserDetail  and getAuthorities are two methods that you can customize to set the authentication and the user Rols according yo your requirements.

 private org.springframework.security.core.userdetails.User userdetails;
 public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        boolean enabled = true;
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;
        com.antuansoft.mongodb.domain.User user = getUserDetail(username);
        
        userdetails = new User(user.getUsername(), 
                    user.getPassword(),
                    enabled,
                    accountNonExpired,
                    credentialsNonExpired,
                    accountNonLocked,
                    getAuthorities(user.getRole()));
            return userdetails;
    }

    public List getAuthorities(Integer role) {
        List authList = new ArrayList();
        if (role.intValue() == 1) {
            authList.add(new SimpleGrantedAuthority("ROLE_ADMIN"));

        } else if (role.intValue() == 2) {
            authList.add(new SimpleGrantedAuthority("ROLE_CAMPAIGN"));
        }
        System.out.println(authList);
        return authList;
    }

    public com.antuansoft.mongodb.domain.User getUserDetail(String username) {
     com.antuansoft.mongodb.domain.User user = userRepositoryDao.findByUsername(username);
        //System.out.println(user.toString());
        return user;
    }


MongoDB:

Here we have two things to consider, Domain classes and Dao interfaces.

1 - Domain classes

This classes are plain old java objects (pojos) to define the data of your database collections, in other words the objects that your are going to persists in your database.

Look at the com.antuansoft.mongodb.domain packge and you will find the two documents that we are going to use in this application. User and Campaign.

The only thing that is special in the classes is that they are annotated with @Document and specify a collection where this pojo will be stored.

@Document(collection = "campaign" )
public class Campaign {


You can open this file to see that.

Campaign.java
User.java

2 - DAO interfaces:

Dao interfacesare in com.antuansoft.mongodb.repositories package and here is where all the database operations (save, delete, etc.) and the domain objects (User, and Campaign) are defined.

In mongo-config.xml file we have defined this package to be scanned to look for mongo repositories, its means that Spring Context transform this interfaces in a DAO object with all the basic operations like create, read, update,delete where we can use in the application.

This classes only have to extend from org.springframework.data.repository.CrudRepository to work.

--
public interface UserRepositoryDao extends CrudRepository<User, String> {
 
 
 public User findByUsername(String username);

}

--

This Crud repository has two params <User, String>, here is  where bind the User domain pojo to this Repository Dao. Here we indicate that this Dao process User Object and the ID´s type is String.

In UserRepositoyDao there is something special, it is a special method user for authentication findByUsername. Another feature of SpringData is that you can define query methods without write any line of code.

Here we have defined a query to find a user by a username, only with key words. find+By+field to query in user pojo object (findByUsername). And it´s work!!!! is magic!!

Spring MVC:

Finally the only thing that we have to review is the controller that allows us to navigate through the application.

The MainController class is a basic class that use the SpringMVC annotations, @RequestMapping,  to navigate:

@RequestMapping(value = "/listCampaigns", method = RequestMethod.GET)
 public String listCampaigns(ModelMap map) {
  
  map.addAttribute("new_campaign", new Campaign());
  Iterable campaings= campaignRepositoryDao.findAll();
  map.addAttribute("campaigns",campaings);
  
  return "listCampaigns";
 }


and basically it means that when SpringMVC detects this urls the method will be executed, in this case the method pass to listCampaigns.jsp all the campaings and new campaing object.

In this class we have a little special thing that add a spring security functionality, it´s the @PreAuthorize annotation that only allow to ROLE_CAMPAIGN users to add a new Campaign.


@PreAuthorize("hasRole('ROLE_CAMPAIGN')")
 @RequestMapping(value = "/addCampaing", method = RequestMethod.POST)
 public String addCampaign(@ModelAttribute(value = "new_campaign") 
                                  Campaign new_campaign,
             BindingResult result){
  
  new_campaign.setId(UUID.randomUUID().toString());
  campaignRepositoryDao.save(new_campaign);
  
  return "redirect:/listCampaigns";
 }

And this its all, i thinks that it is a complete example a not too difficult to understand.

Remember that you have all the code in my GitHub repository.


References:

Mainly Spring Security 3.x Cookbook

Spring Data MongoDB
Spring Security
Spring MVC


jueves, 24 de octubre de 2013

Scripts para MongoDB con Javascript y JSON: ejemplo práctico

Title: Scripts for MongoDB with Javascript and JSON.

En esta entrada quiero ilustrar como se hace un script de actualización de datos para MongoDB utilizando el lenguaje de programación javascript.

Los datos en MongoDB están almacenados en JSON y vamos ver como poblar una base de datos con unos cuantos objectos JSON y también como realizar unas sencillas manipulaciones de los objectos creados.

Toda la información que aquí os pongo es todo lo que necesitáis para empezar a lanzar scripts a cualquier base de datos MongoDB situada en cualquier host o ip.

Se da por hecho que la instalación de MongoDB ya está hecha, sino puedes consultar aquí como instalarlo en Windows 7 de una manera muy sencilla.

Los pasos para crear y lanzar nuestros scripts son los siguientes:

1 - Arrancar motor de MongoDB
2 - Abrir una ventana de comandos y situarnos en el directorio bin de mongo.
3 - Creación del script de poblado de base de datos
4 - Lanzamiento del script.
5 - Comprobación  de datos
6 - Creación de script de actualización
7 - Lanzamiento del script.
8 - Comprobación  de datos

Nota: Siempre tenemos que tener un MongoDB instalado en nuestra máquina local donde realizaremos las operaciones de lanzamiento del script, pero esté script podrá ejecutar en cualquier instalación de MongoDB que tengas en remoto siempre que conozcas su host y puerto.


1 - Arrancar motor de MongoDB

Lo arrancamos yendo a la ruta de instalación de MongoDB y ejecutando: mongod --dbpath ..\data\db

[MONGODB-PATH]\bin\mongod --dbpath ..\data\db




2 - Abrir una ventana de comandos y situarnos en el directorio bin de mongo

En windows sería:

cmd
cd [MONGODB-PATH]\bin



3 - Creación del script de poblado de base de datos

Con nuestro editor favorito creamos un script en lenguaje javascript para ejecutarlo en la instancia de MongoDB que queramos.

Los aspectos importes que tenemos que tener en cuenta son los siguientes que se entenderán perfectamente al ver el código:

  • Tendremos que tener creada ya, una base de datos con su nombre. Si no la tenemos creada no pasa nada porque al utilizarla por primera vez e insertar en ella se creará.          
  •  En el propio script elegimos  el host o direccion ip del mongodb donde se ejecutará este script y la base de datos donde se aplicará. Es decir que es en el propio script donde indica el destino de todo.
  • Al ser MongoDb una base de datos NoSQL no es necesario crear una estructura de datos previa directamente iremos insertando nuestros datos con código javascript y estructura JSON.
  • El ID de los registros prefiero introducirlo yo manualmente porque si dejamos que el propio Mongodb nos lo autogenere no lo hace como un número entero normal y a la hora de buscarlo desde una aplicación puede resultar complicado. Así que recomiendo generar los ID de los registros en el servidor con un generador de ids único y añadirlo a tus registros, otra opción es generar el código con una función en javascript dentro del propio script.


El script de relleno de la base de datos es el siguiente:



--

/*
 * Script que se encarga de poblar la base de datos  
 * 
 */

print("STARTING SCRIPT");
//Host donde está nuestra base de datos, no tiene que ser nuestro equipo local, puede ser cualquier mongoDb.
conn = new Mongo("localhost");
//Nombre de la base de datos que vamos a utilizar
db = conn.getDB("antuandb");

/*Limpiamos la base de datos por si existia algo antes*/
db.dropDatabase();


/*coleciones de nuestro modelo de datos*/
db.createCollection("users");
db.createCollection("courses");

/* Usuarios */
print("***********creating users*********");


user1 = {
  
"_id" : "0",
"name" : "Antuan",
"surname" : "Martín",
"age": 35,
"deleted":false

}

user2 = {
  
"_id" : "1",
"name" : "Felime",
"surname" : "Rodriguez",
"age": 31,
"deleted":false

}


user3 = {
  
"_id" : "2",
"name" : "Jose",
"surname" : "Carrizo",
"age": 28,
"deleted":false

}


print("***********creating courses*********");

/* courses */


course1 = {
     
    "_id" : "0",
    "students" : [],
    "tutors" : [],
    "name":"Master en Animación y Modelado 3d con Maya en Madrid",
    "credits":3.5,
    "hours":35
  
};

course2 = {
   
    "_id" : "1",
    "students" : [],
    "tutors" : [],
    "name":"SEO: Posicionamiento Web en Buscadores",
    "credits":4.0,
    "hours":40
  
};

course3 = {
   
    "_id" : "2",
    "students" : [],
    "tutors" : [],
    "name":"Máster en Game design",
    "credits":2.8,
    "hours":28  
};


print("***********saving users*********");
db.users.save(user1);
db.users.save(user2);
db.users.save(user3);

print("***********saving courses*********");
db.courses.save(course1);
db.courses.save(course2);
db.courses.save(course3);


print("SCRIPT FINISHED");

--

4 - Lanzamiento del script.

Ya tenemos el script guardado en un archivo por ejemplo llamado initDB.js y estamos situados en el directorio [MONGODB-PATH]\bin como indicamos en el apartado 2.

Para larzar el script ejecutamos el siguiente comando.

mongo archivo1.js [archivon.js]

en nuestro caso

mongo initDB.js

Nota: Podemos poner tantos archivos .js como utilicemos, o sea que se pueden dividir los scripts en varios archivos.


Si tenemos algún error durante la ejecución podemos verlo en la salida de la ventana de comandos, tras la ejecución del script, donde nos dirá el error cometido y la línea en la que se ha producido, en este caso la línea 71 y nos se ha producido un SyntaxError.

--

MongoDB shell version: 2.4.1
connecting to: test
Mon Oct 21 18:12:09.559 JavaScript execution failed: SyntaxError: 
Unexpected string at C:\......\initDB.js:L71
failed to load: C:\..............\initDB.js

--


Una vez ejecutado el script correctamente obtengo la siguiente salida.

--
C:\mongodb-win32-2.4.1\bin>mongo initDB.js
MongoDB shell version: 2.4.1
connecting to: test
STARTING SCRIPT
***********creating users*********
***********creating courses*********
***********saving users*********
***********saving courses*********
SCRIPT FINISHED

--

No hay que hacer caso de lo del mensaje:

connecting to: test

porque es la base de datos de conexión por defecto.



5 - Comprobación  de datos

Yo recomiendo siempre que ejecutes un script comprobar los datos que se han introducido en la base de datos sean correctos tanto en número de registros como en los tipos de datos, porque podemos llevarnos sorpresas y quizás lo que era un Double es un String o lo que debia de ser un Array es un String y eso es un error grave.

Con el software MongoVUE que ya os he recomendado podemos ver esto sin problemas.

NOTA: No os fieis nunca del modo texto porque no deja de ser una interpretación del JSON, yo siempre me voy a la vista en arbol para comprobar los tipos de datos de cada campo (String, Double, Arry, etc..).



Si no te has descargado MongoVE, lo cual es un error muy grande, tienes la opción de ver los resultados por consola, para ello ejecutamos lo siguiente en una ventana de comandos:

mongo

y entramos en la shell de mongo, donde podremos operar con la base de datos realizar las siguiente operaciones, una para usar la base de datos que nos hace falta y la segunda para realizar la query de consulta de los datos.

> use antuandb
> db.users.find()

Y podremos ver el resultado en pantalla pero queda muy feo y no podemos ver bien los tipos de datos.






6 - Creación de script de actualización

Una vez que hemos creado un script de llenado de la base de datos ahora vamos a hacer un script de actualización de los datos que tenemos.

Vamos a hacer dos modificaciones sobre los datos para que podamos ver varios ejemplos de actualización.

  • Primero: vamos añadir un campo nuevo a todos los cursos que es deleted=false y sumaremos una hora al campo hours.
  • Segundo: vamos agregar a los usuarios menores de 30 a los cursos como alumnos y a los mayores de 30 como tutor.

Cosas importantes a tener en cuenta:

  • Al realizar una query los datos se almacenan una variable de tipo cursor que podemos recorrer y a su vez nos darán funciones interesantes como count() o forEach(funciton).
  • En la query donde buscamos los cursos debemos de utilizar la función snapshot porque si no la usamos al realizar modificaciones sobre los propios objetos que están en el cursos de resultados este se podría ver alterado. Usando snapshot garantizamos que los datos recuperados en ese cursos no se alterarán.
  •  La manera de recorrer los datos es usando la función forEach que incluirá una función para procesar cada unos de los datos del cursor.
  • lo la función addToSet introducimos un valor en un array del objeto de MongoDB de tal modo que si el objeto ya existe este no se inserta 2 veces. Esto lo hacemos para introducir el id del usuario en el array de alumnos o de tutores del curso.



--
/*
 * Script que se encarga de actualizar la base de datos  
 * 
 */

print("STARTING SCRIPT");
//Host donde está nuestra base de datos, no tiene que ser nuestro equipo local, puede ser cualquier mongoDb.
conn = new Mongo("localhost");
//Nombre de la base de datos que vamos a utilizar
db = conn.getDB("antuandb");

var i_int = 0;
var modificados_int = 0;
var ignorados_int = 0;
var erroneos_int = 0;

//Recuperamos todos los cursos
var courses_cur = db.courses.find().snapshot();
var totalSize_int = courses_cur.count();

print("******FIRST PART UPDATING COURSES************");

courses_cur.forEach(function(course){
 i_int++;
 print("*********************************");
    print("RECORD "+i_int +" of " + totalSize_int);   
 print("PROCESSING:"+ course.name + "("+course._id+")");
 
 //añado la nueva propiedad
 course.deleted = false;
 
 //Modifico propiedad existente
 course.hours = course.hours + 1;
   
 db.courses.save(course);
 
 print("UPDATED");
});

print("******END FIRST PART************");

print("******SECOND PART ADDING STUDENTS AND TUTORS************");

//Recorro todos los cursos
courses_cur = db.courses.find().snapshot();
courses_cur.forEach(function(course){
   print("*********************************");
   print("PROCESSING COURSE:"+ course.name + "("+course._id+")");
   var id_course = course._id;
   
   //Recupero los usario mayores de 30 años
   var users_cur = db.users.find({age:{$gt:30}}).snapshot();
         
   users_cur.forEach(function(user){
    
    print("      PROCESSING USER:"+ user.name + "("+user._id+")");
    var id_user = user._id;    
    db.courses.update({_id:id_course},{$addToSet:{"students":id_user}});
    
    print("      USER ADDED AS STUDENT");
 
   });
   
   //Recupero los usuarios menores de 30 años
   var users_cur = db.users.find({age:{$lte:30}}).snapshot();
   users_cur.forEach(function(user){
    
    
    print("      PROCESSING USER:"+ user.name + "("+user._id+")");
    var id_user = user._id;    
    db.courses.update({_id:id_course},{$addToSet:{"tutors":id_user}});
    
    print("      USER ADDED AS TUTOR");
    
   });      
 
});




--



7 - Lanzamiento del script.

Una vez guardado el script en el archivo updateDB.js lo ejecutamos igual que hicimos en el paso 4

mongo updateDB.js

La salida es la siguiente




--
C:\mongodb-win32-2.4.1\bin>mongo updateDB.js
MongoDB shell version: 2.4.1
connecting to: test
STARTING SCRIPT
******FIRST PART UPDATING COURSES************
*********************************
RECORD 1 of 3
PROCESSING:Master en Animación y Modelado 3d con Maya en Madrid(0)
UPDATED
*********************************
RECORD 2 of 3
PROCESSING:SEO: Posicionamiento Web en Buscadores(1)
UPDATED
*********************************
RECORD 3 of 3
PROCESSING:Máster en Game design(2)
UPDATED
******END FIRST PART************
******SECOND PART ADDING STUDENTS AND TUTORS************
*********************************
PROCESSING COURSE:Master en Animación y Modelado 3d con Maya en Madrid(0)
      PROCESSING USER:Antuan(0)
      USER ADDED AS STUDENT
      PROCESSING USER:Felipe(1)
      USER ADDED AS STUDENT
      PROCESSING USER:Jose(2)
      USER ADDED AS TUTOR
*********************************
PROCESSING COURSE:SEO: Posicionamiento Web en Buscadores(1)
      PROCESSING USER:Antuan(0)
      USER ADDED AS STUDENT
      PROCESSING USER:Felipe(1)
      USER ADDED AS STUDENT
      PROCESSING USER:Jose(2)
      USER ADDED AS TUTOR
*********************************
PROCESSING COURSE:Máster en Game design(2)
      PROCESSING USER:Antuan(0)
      USER ADDED AS STUDENT
      PROCESSING USER:Felipe(1)
      USER ADDED AS STUDENT
      PROCESSING USER:Jose(2)
      USER ADDED AS TUTOR
SCRIPT FINISHED

--


8 - Comprobación  de datos

Finalmente realizamos la comprobación de que que los datos se han usertado correctamente, recomiendo hacerlo porque MongoDB no da errores si la inserción no es correcta o te equivocas en una query, directamente seguirá ejecutando el código como nada hubiera pasado.

echando un ojo a los datos vemos que todo está correcto y ya está todo terminado.



--

{
  "_id" : "0",
  "credits" : 3.5,
  "deleted" : false,
  "hours" : 41.0,
  "name" : "Master en Animación y Modelado 3d con Maya en Madrid",
  "students" : ["0", "1"],
  "tutors" : ["2"]
}

{
  "_id" : "0",
  "name" : "Antuan",
  "surname" : "Martín",
  "age" : 35.0,
  "deleted" : false
}

--


Espero que os resulte muy útil y si es así se agradece un comentario. También se aceptan críticas y mejoras de mismo, así como dudas.

Referencias:

Write Scripts for the mongoDb Shell




viernes, 14 de junio de 2013

MongoDB: Hacer una query sobre campos del propio documento

La problemática de hoy surge porque me he visto en la necesidad de ver si dos campos de un mismo documento de mongoDb son iguales.

Para ello lo que necesito es hacer una query buscando el contenido de uno de los campos del documento en otro.

En mi caso tengo que recuperar los documentos de la tabla de usuarios donde el username sea igual a email de contacto.

{
  "_id" : "6006491b-04cc-45be-82cb-5612395d2d10",
  "username" : "xxxx@xxx.com",
  "password" : "xxxxxxx",
  "name" : "Manuel",
  "surname" : "Roberto Del Pino",
  "email" : "xxxx@xxx.com",
  "dni" : "",
  "phone" : "",
  "creationDate" : ISODate("2013-02-15T12:22:29.476Z"),
  "activated" : true,
  "deleted" : false,
  "address" : "",
  "city" : "",
  "province" : "",
  "postalCode" : "",
  "country" : "",
  "activationDate" : ISODate("2013-02-15T12:22:29.476Z")
}

Para ello se puede utilizar la siguiente query usando la clausila $where con una instrucción en javascript.

 {$where:"this.email == this.username"}

El resultado de la ejecución será más lento de lo habitual porque en motor de javascript entra en funcionamiento.

No es recomendable para coleccione con muchos documentos.

Referencia:
Mongodb query with fields in the same documents

viernes, 17 de mayo de 2013

SpringData MondoDb: Configuración de conexiones PRODUCCION

Establecemos los valores de la conexión de SpringData para MongoDb en producción basándonos en el siguiente artículo encontrado en StackOverflow: How to configure MongoDB Java driver MongoOptions for production use?

auto-connect-retry="true" : para que el servidor se reconecte se pierde la conexión.

connections-per-host="40"  :  Conexiones que tendrá nuestra aplicación, son la que va a establecer del driver de java para esta aplicación.

connectTimeout = "30000": El el tiempo que espera el servidor antes de abortar un intento de conexión con la base de datos. O sea es el tiempo que espera para conseguir una conexión libre. Debe de ser algo grande entre 15 y 30 segundos para que sea realista según recomiendan.

socketTimeout="60000" : Una vez iniciada un operación de lectura, es tiempo que el socket va  a esperar la respuesta, si se supera podremos tener errores del tipo:

--
Caused by: com.mongodb.MongoException$Network: can't say something
 at com.mongodb.DBTCPConnector.say(DBTCPConnector.java:194)
 at com.mongodb.DBTCPConnector.say(DBTCPConnector.java:155)
 at com.mongodb.DBApiLayer$MyCollection.update(DBApiLayer.java:349)
 at com.mongodb.DBCollection.update(DBCollection.java:177)
 at com.mongodb.DBCollection.save(DBCollection.java:817)
 at com.mongodb.DBCollection.save(DBCollection.java:785)
 at org.springframework.data.mongodb.core.MongoTemplate$10.doInCollection(MongoTemplate.java:885)
 at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:388)

Caused by: java.net.SocketTimeoutException: Read timed out
 at java.net.SocketInputStream.socketRead0(Native Method)
 at java.net.SocketInputStream.read(SocketInputStream.java:150)
 at java.net.SocketInputStream.read(SocketInputStream.java:121)
 at java.io.BufferedInputStream.fill(BufferedInputStream.java:235)
 at java.io.BufferedInputStream.read1(BufferedInputStream.java:275)
 at java.io.BufferedInputStream.read(BufferedInputStream.java:334)
 at org.bson.io.Bits.readFully(Bits.java:46)
--

Referencia útil: connectionTimeout VS SocketTimeout

maxWaitTime = "1500" : Milisegundos que un hilo esperará  para conseguir una conexión libre del pool de conexiones.

Aquí os dejo la configuración que por ahora nos funciona correctamente.
--
<mongo:mongo host="xxx.xxx.xxx" port="xx" id="mongo">
   <mongo:options connections-per-host="40"
            threads-allowed-to-block-for-connection-multiplier="1500"
            connect-timeout="30000"
            max-wait-time="1500"
            auto-connect-retry="true"
            socket-keep-alive="true"
            socket-timeout="60000"
            slave-ok="true"
            write-number="1"
            write-timeout="0"
            write-fsync="true"/>
</mongo:mongo>

--

jueves, 9 de mayo de 2013

Instalación de MongoDb en Windows 7


En este rápido post vamos a ver como se instala de una manera muy fácil MongoDB en nuestro sistemas Windows.

1 - Para hacerlo lo que necesitamos es descargar MongoDB a nuestra ordenador e instalarlo (o descompromirlo) según la versión que utilicemos aquí podemos encontrarlo el paquete de descarga.

Dependiendo del sistema operativo que utilicemos descargaremos una u otra en mi caso con Windows 7



2 - Descomprimimos el zip en la ruta que queramos, en mi caso lo he puesto c: de tal modo que el directorio de instalación es C:\mongodb-win32-2.4.1

3 - Debemos de crear un directorio para guardar los archivos de base de datos, la documentación nos da un directorio de instalación por defecto que es c:\data\db. Podemos crearlo o establecer uno distinto en los parámetros de arranque de mongo con la opción --dbpath

Yo prefiero definir el directorio dentro de la propia carpeta de instalación por lo tanto  creo la siguiente ruta de directorios

C:\mongodb-win32-2.4.1\data\db

4  - Creación del script de arranque:  Para no tener que estar poniendo el comando de arranque todo el tiempo creo un archivo start_mongo.bat dentro del directorio bin, con el siguiente código:


mongod.exe --dbpath ..\data\db

5 - Arrancamos mongo: Ejecutamos el script de arranque pulsando sobre nuestro archivo .bat o desde una ventana de commando de windows y desde el directorio bin escribimos el código del punto 4.


Ya tenemos arrancada nuestra base de datos.

6 - Ahora arrancamos la Shell de MongoDB para poder operar con la base de datos.

Desde una nueva ventana de comandos accdemos al direcorio bin y ejecutamos:

mongo.exe




Ya podemos operar con nuestra base de datos en LOCAL.

Para operar podemos ver las base de datos que tenemos disponibles:

show dbs

y seleccionamos la base de datos que deseemos con el comando user.

use test

Una vez seleccionada la base de datos ya podemos insertar registros

db.test.save({a:1})

y recuperarlos:

db.test.find()




Ya tenemos la base de datos lista para operar.











jueves, 14 de febrero de 2013

MongoVUE: Cliente de MongoDB para Windows: un SQLYog para MongoDB

Nosotros hasta ahora utilizábamos RockMongo que es un cliente WEB basado en PHP y como producto está bien te permite crear, borrar, exportar e importar bases de datos, consultar, insertar y ejecutar funciones javascript.

Pero para mi uno de los problemas es que está basado en web y tienes que tener un servidor web para montarlo y es poco cómodo.



Se echa de menos una solución parecida a SQLYog para MySQL donde es instalas un cliente configuras tus conexiones desde el cliente y ya puedes acceder.

Pues la opción de este tipo para MongoDB es MONGOVUE, los instalas pones tu conexión y nos permite las mismas funciones que rockmongo y de una manera bastante intuitiva.




Referencia:

http://stackoverflow.com/questions/4269688/mongo-interface

jueves, 16 de agosto de 2012

Spring Data: Artículo imprescindible de introducción a Spring Data

Os dejo el enlace a este artículo que está muy bien para saber de que va el tema de Spring Data. Nosotros utilizamos SpringData Mongodb y SpringData redis.

http://www.infoq.com/articles/spring-data-intro

viernes, 10 de agosto de 2012

MongoDB: java.lang.NoClassDefFoundError: com/google/common/base/Function

Este error se produce por no tener las versiones correctas de las librerías de Spring-data y query-dsl


--
java.lang.NoClassDefFoundError: com/google/common/base/Function
 at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.createQueryFor(QueryDslMongoRepository.java:167)
 at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.count(QueryDslMongoRepository.java:154)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
 at $Proxy26.count(Unknown Source)
 at com.antuansoft.contentapi.application.CourseContentTemplateImpl.countByAllowedUsers(CourseContentTemplateImpl.java:61)
 at com.antuansoft.contentapi.integration.CourseContentTemplateIntegrationTest.countByAllowedUsers(CourseContentTemplateIntegrationTest.java:157)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:45)
 at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
 at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
 at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
 at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
 at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
 at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
 at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
 at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
 at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
 at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
 at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
 at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
 at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
 at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
 at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
 at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
 at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
 at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
 at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.ClassNotFoundException: com.google.common.base.Function
 at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
 ... 43 more

--

Si investigamos un poco en google la clase que nos fata la podemos encontrar en un jar llamado guava-12.0.jar este error se soluciona pero nos da otro


--
java.lang.NoSuchMethodError: com.mysema.query.mongodb.MongodbQuery.<init>(Lcom/mongodb/DBCollection;Lcom/google/common/base/Function;Lcom/mysema/query/mongodb/MongodbSerializer;)V
 at org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery.<init>(SpringDataMongodbQuery.java:58)
 at org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery.<init>(SpringDataMongodbQuery.java:44)
 at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.createQueryFor(QueryDslMongoRepository.java:167)
 at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.count(QueryDslMongoRepository.java:154)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)

--
Para este error ya tengo un post publicado que este:

Spring JPA MongoDB: java.lang.NoSuchMethodError: com.mysema.query.mongodb.MongodbQuery

jueves, 9 de agosto de 2012

MongoDB Spring Data: Push de objetos en Arrays

Push an Object into an array (addToSet)

Si trabajáis con MongoDB ya sabréis que podemos utilizar el comando push para introducir valores en un arrary.

Por ejemplo si tenemos el siguiente objecto mapeado en MongoDB


import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import com.google.code.morphia.annotations.Entity;

@Entity
@Document
public class Course implements Serializable {
 
 
 private static final long serialVersionUID = -7251789829032925885L;
 
 
 @Id
 private String id = UUID.randomUUID().toString();
 private String name; 
 private String description;
 private long creationDate = new Date().getTime();
 private int duration;
 private int credits;
 private List<String> tags = new ArrayList<String>();
 private List<String> users = new ArrayList<User>();


Si queremos introducir un valor String en el array de tags es tan fácil como hace esto

 public void addTag(String courseId, String tag){
  
  

    
  WriteResult wr = mongoOperations.updateFirst(
   new Query(where("id").is(courseId)),   
   new Update().push("tags", tag),Course.class);
   
    

  
  
 }


El resultado en MongoDb debería ser algo parecido a esto


  {"_class": "com.antuansoft.Course",
  
  "_id": "953a878e-5aa6-4811-82e3-660b00ebafe2",
    
  "creationDate": 1344439353237,
  
  "credits": 10,
  
  "description": "Course DESCRIPTION",
  
  "duration": 100,
    
  "name": "Course 1",
    
  "tags": {
    
    "0": "tag1"
    "1": "tag2"
  
  }}

Al insertar en el array de tags objetos de la clase String no hay ningún problema ya que los entiende perfectamente.

El problema que he encontrado ocurre cuando tienes que insertar en el array Objectos de alguna clase que no sean tipos básicos. Por ejemplo un objecto de la clase User.

El problema que ocurre es que al insertarlo de la misma manera que un String normal no transforma el objeto y no pone las propiedades ni "_id" ni "_class", o sea que no lo transforma e introduce sólo las propiedades que tenga ese objeto en el array.

Para solucionar esto utilizamos una par de instrucciones que fuerzan a MongoDb a realizar esta transformación.

DBObject dbo = new BasicDBObject();
mongoOperations.getConverter().write(user, dbo);


public void addUser(String idCourse, User user){  
  
   
   DBObject dbo = new BasicDBObject();
   mongoOperations.getConverter().write(user, dbo);
   
   WriteResult wr = mongoOperations.updateFirst(
     new Query(where("id").is(idCourse)),   
     new Update().push("users", dbo),Course.class);


{"_class": "com.antuansoft.Course",
  
  "_id": "953a878e-5aa6-4811-82e3-660b00ebafe2",
    
  "creationDate": 1344439353237,
  
  "credits": 10,
  
  "description": "Course DESCRIPTION",
  
  "duration": 100,
    
  "name": "Course 1",
    
  "tags": {
    
    "0": "tag1"
    "1": "tag2"
  
  }

"users": {
    "0": {
        
        "_class": "com.antuansoft.User",
        
        "_id": "047cd911-356a-4851-948a-3087ea735353",
        "name": "AntuanSoft"
      }
  }
  
  }

Nota: Si no queremos que aparezcan valores repetidos en nuestra lista de objetos/strings lo mejor es utilizar el método  addToSet en vez de push. De este modo si introducimos un valor que ya existe, este no se insetará

viernes, 27 de julio de 2012

MongoDb: DataAccessResourceFailureException

Configurando uno de nuestros test sobre los datos almacenados en MongoDB sobre un nuevo dbmane (database-test) no creado ya en MongoDB me ha dado el siguiente error:

--
org.springframework.dao.DataAccessResourceFailureException: can't say something; nested exception is com.mongodb.MongoException$Network: can't say something
 at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:56)
 at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:1545)
 at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:368)
 at org.springframework.data.mongodb.core.MongoTemplate.doRemove(MongoTemplate.java:928)
 at org.springframework.data.mongodb.core.MongoTemplate.remove(MongoTemplate.java:919)
 at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.delete(SimpleMongoRepository.java:137)
 at org.springframework.data.mongodb.repository.support.SimpleMongoRepository.delete(SimpleMongoRepository.java:146)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
 at $Proxy52.delete(Unknown Source)

--

La configuración utilizada fue la siguiente:

--
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:flex="http://www.springframework.org/schema/flex" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:mvc="http://www.springframework.org/schema/mvc"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:p="http://www.springframework.org/schema/p"
 xmlns:security="http://www.springframework.org/schema/security"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:mongo="http://www.springframework.org/schema/data/mongo"
 xmlns:jpa="http://www.springframework.org/schema/data/jpa"
 xmlns:task="http://www.springframework.org/schema/task"
 xsi:schemaLocation="
  http://www.springframework.org/schema/beans
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/data/mongo
        http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd
  http://www.springframework.org/schema/flex
  http://www.springframework.org/schema/flex/spring-flex-1.5.xsd
  http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
  http://www.springframework.org/schema/tx
  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
  http://www.springframework.org/schema/task
  http://www.springframework.org/schema/task/spring-task-3.0.xsd
  http://www.springframework.org/schema/aop
  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
  http://www.springframework.org/schema/data/jpa
     http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">

 
 <mongo:mongo host="apps.stratmind.net" port="27017" id="mongo">
         <mongo:options connections-per-host="8"
                    threads-allowed-to-block-for-connection-multiplier="4"
                    connect-timeout="1000"
                    max-wait-time="1500"
                    auto-connect-retry="true"
                    socket-keep-alive="true"
                    socket-timeout="1500"
                    slave-ok="true"
                    write-number="1"
                    write-timeout="0"
                    write-fsync="true"/>
    </mongo:mongo>

 <mongo:repositories base-package="com.antuansoft.mongo"/>

 <mongo:db-factory dbname="database-test" mongo-ref="mongo" id="mongoDbFactory"/>
 
 <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
    <property name="writeResultChecking" value="EXCEPTION"/><!-- propiedad usada para que si hay error devuelva una excepción y no haya que comprobarlo -->    
 </bean>


</beans>
--


El error se produce cuando no tengo creada de antemano la base de datos  de mongo, si la base de datos ya está creada no hay problema.

Parece que Spring Data al crearla y al intentar acceder a la base de datos recien creada recibe un error del driver de MongoDb y a fecha de hoy 27/07/2012 (driver version: 2.7.3) existe un bug en este apartado.

Como referencia os dejo esta entrada en el JIRA de MongoDB en su versión del driver para java.

mongo driver should check whether socket still be available before use it?

jueves, 26 de julio de 2012

Problemas típicos de configuración de los Repositories de JPA y Mongodb combinados

Typical problems configuring JPA and Mongodb Repositories together

Utilizando Spring Data tenemos la posibilidad de acceder fácilmente a datos almacenados en base de datos relacionales como Mysql, Postgres, etc.. a través de Spring data JPA y también a base de datos NoSQL como por ejemplo MongoDB con Spring Data MongoDB.

Una de las principales carácterísticas de Spring Data es que ya no es necesaria la generación de classes DAO con las opreciones básicas como save, delete, update y find ya que ya vienen implementadas por defecto.

Para que eso funcione creamos una interfaz que heredará de una clase de Spring Data y ya tendrá todas las operaciones disponibles.


JPA (AnnouncementDao.java)
-
package com.antuansoft.daos.jpa
import org.springframework.data.jpa.repository.JpaRepository;

import com.antuansoft.Announcement;

public interface AnnouncementDao extends JpaRepository<Announcement, String>, CustomAnnouncementDao{


}


--

MongoDB (CompanuDao.java)


-
package com.antuansoft.daos.mongodb
import java.util.List;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;

import com.antuansoft.pojo.Company;

public interface CompanyDao extends MongoRepository<Company, String>, QueryDslPredicateExecutor<Company>{

}
--

Para que estos daos funcionen correctamente en nuestro contexto de Spring tenemos que  configurarlo en los xml a través de unos repositories que mapeen estas interfaces indicándole en la opción base-package que páquete(o alguno de sus hijos) contienen los DAOS.


Ambas opciones podemos combinarlas en nuestra arquitectura de datos pero lo normal cuando integramos las dos maneras de acceso dentro de un mismo proyecto es que nos den los siguientes errores:

1º Error típico:

-
Caused by: java.lang.IllegalArgumentException: Not an managed type: class com.antuansoft.pojos.Company
 at org.hibernate.ejb.metamodel.MetamodelImpl.managedType(MetamodelImpl.java:171)
 at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:58)
 at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getMetadata(JpaEntityInformationSupport.java:65)
 at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:149)
 at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:87)
 at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:70)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:137)
 at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:125)
 at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:41)
 at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
 ... 63 more

--


Este error ocurre por que JPA ha mapeado un DAO de Mongodb, esto ocurre cuando el base-package de ambas configuraciones es el mismo o el de mongo está dentro del de JPA. Por lo tanto JPA intentará configurar un DAO de Mongodb y no podrá procesar la Clase Company que es de Mongobd.

La configuración correcta sería que los paquetes donde están los daos sean distintos y no estén anidados.


JPA
-
<jpa:repositories base-package="com.antuansoft.daos.jpa"></jpa:repositories>
--


Mongodb
-
<mongo:repositories base-package="com.antuansoft.daos.mongodb"/>
--


2º Error típico:

El pojo del interface DAO no está configurados correctamente a través de la etiqueta  @Entity

Los pojos de JPA utilizan la @Entity
-
import javax.persistence.Entity;
--

Los pojos de MongoDB utilizan la @Entity
-
import com.google.code.morphia.annotations.Entity;
--

Si configuramos un pojo de MongoDb con una entity de JPA nos dará el siguiente error
-
Caused by: org.hibernate.AnnotationException: No identifier specified for entity: com.antuansoft.pojos.Company
 at org.hibernate.cfg.InheritanceState.determineDefaultAccessType(InheritanceState.java:268)
 at org.hibernate.cfg.InheritanceState.getElementsToProcess(InheritanceState.java:223)
 at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:686)
 at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:3977)
 at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3931)
 at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1368)
 at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1345)
 at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1477)
 at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:193)
 at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:1096)
 at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:685)
 at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:73)
 at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:268)
 at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1514)
 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1452)
--

Para finalizar os dejo unas referencias interesantes para ver como realizar estas configuraciones básicas.

Configuración basica de Spring Data Mongobd (mirad las otras 2 partes tb están bien)
Capa de persistencia con Spring Data JPA

miércoles, 11 de julio de 2012

Spring Data y QueryDSL operador like

QueryDSL like operator

Prerrequisitos: Conocimientos medios de QueryDsl.

El problema que resolvemos a continuación es el operador like usando QueryDSL. Si no quieres leerte todo el rollo te adelanto que el operador que buscas es containsIgnoreCase en vez de like pero tiene alguna pega que expongo a continuación.

Si has llegado hasta aquí buscando por google supongo que ya tendrás configurado tu entorno y QueryDsl para realizar tus consultas. Como ya sabemos la ventaja que te da QueryDsl es nos permite construir una query directamente sobre el código java de nuestro service.

Imaginemos que tenemos  el siguiente pojo

----
@Entity
@Document
public class Course implements Serializable {
 
 
 private static final long serialVersionUID = -7251789829032925885L;
 
 
 @Id
 private String id = UUID.randomUUID().toString();
 private String name; 
 private String description;
 private String companyOwner;
 private String metodology;

---

Una vez configurado QueryDsl automáticamente nos lo transforma nuestro pojo en el siguiente código.

----
/**
 * QCourse is a Querydsl query type for Course
 */
@Generated("com.mysema.query.codegen.EntitySerializer")
public class QCourse extends EntityPathBase<Course> {

    private static final long serialVersionUID = 430564814;

    public static final QCourse course = new QCourse("course");

    public final StringPath companyOwner = createString("companyOwner");

    public final StringPath description = createString("description");

    public final StringPath id = createString("id");

    public final StringPath metodology = createString("metodology");


    public QCourse(String variable) {
        super(Course.class, forVariable(variable));
    }

    public QCourse(Path<? extends Course> entity) {
        super(entity.getType(), entity.getMetadata());
    }

    public QCourse(PathMetadata<?> metadata) {
        super(Course.class, metadata);
    }


---

Esto nos permite hacer queries directamente con código java

----
BooleanBuilder builder = new BooleanBuilder();
   
   builder.and(QCourse.course.id.eq("id1"));
   builder.and(QCourse.course.name.like("Matematica"));

List<Course> data = (List<Course>) courseDao.findAll(builder.getValue()); 
      
   return data;

---


El problema es cuando queremos hacer una búsqueda sobre un campo String con lo que en SQL sería el operador LIKE, como por ejemplo Name like %Matematica% 

QueryDsl tiene el operador like pero su lo utilizamos nos da el siguiente error:
 
 
----
java.lang.UnsupportedOperationException: Illegal operation course.name like Matemáticas
 at com.mysema.query.mongodb.MongodbSerializer.visit(MongodbSerializer.java:225)
 at com.mysema.query.mongodb.MongodbSerializer.visit(MongodbSerializer.java:34)
 at com.mysema.query.types.expr.BooleanOperation.accept(BooleanOperation.java:52)
 at com.mysema.query.mongodb.MongodbSerializer.handle(MongodbSerializer.java:39)
 at com.mysema.query.mongodb.MongodbSerializer.visit(MongodbSerializer.java:98)
 at com.mysema.query.mongodb.MongodbSerializer.visit(MongodbSerializer.java:34)
 at com.mysema.query.types.OperationImpl.accept(OperationImpl.java:81)
 at com.mysema.query.mongodb.MongodbSerializer.handle(MongodbSerializer.java:39)
 at com.mysema.query.mongodb.MongodbQuery.createQuery(MongodbQuery.java:330)
 at com.mysema.query.mongodb.MongodbQuery.createCursor(MongodbQuery.java:240)
 at com.mysema.query.mongodb.MongodbQuery.createCursor(MongodbQuery.java:235)
 at com.mysema.query.mongodb.MongodbQuery.list(MongodbQuery.java:221)
 at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.findAll(QueryDslMongoRepository.java:113)


---
 
   
  La alternativa para que la operación sea la que deseamos es containsIgnoreCase


----

builder.and(QCourse.course.name.containsIgnoreCase("Matemati"));

---
 
  
  Esta búsqueda recuperará cualquier palabra en el nombre que contenga Matemati como por ejemplo, Matematica, Matematico, Matematicas ya esté en mayúsculas o minúsculas.


Lo que no encontrará este tipo de búsqueda son acentos, por ejemplo Matemáticas, así que no es una búsqueda full text pero se aproxima bastante.




miércoles, 4 de julio de 2012

Spring Data Mongodb Configurando WriteResultChecking Policy para lanzar excepciones

Setting WriteResultChecking Policy in order to throw Exceptions

Cuando trabajamos con Mongodb a la hora de hacer Queries debemos de comprobar el objeto WriteResult resultante para comprobar que la operación se ha realizado correctamente.


WriteResult wr = mongoOperations.updateFirst(
 new Query(where("id").is("objectId")),   
 new Update().push("favouriteUsers", idUser),Course.class);
  
 String error = wr.getError();




Para que no tengmos que hacer esto y si ocurre algún problema se lance una excepcion debemos de configurar Spring Data Mongo Db de la siguiente manera.

<mongo:mongo host="localhost" port="27017" id="mongo">
         <mongo:options connections-per-host="8"
                    threads-allowed-to-block-for-connection-multiplier="4"
                    connect-timeout="1000"
                    max-wait-time="1500"
                    auto-connect-retry="true"
                    socket-keep-alive="true"
                    socket-timeout="1500"
                    slave-ok="true"
                    write-number="1"
                    write-timeout="0"
                    write-fsync="true"/>
    </mongo:mongo>

 <mongo:repositories base-package="xx.xxxx.xxx"/>

 <mongo:db-factory dbname="database" mongo-ref="mongo" id="mongoDbFactory"/>
 
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
    <constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
    <property name="writeResultChecking" value="EXCEPTION"/>
<!-- propiedad usada para que si hay error devuelva una excepción y no haya que comprobarlo -->    
 </bean>


La clave está en la propiedad writeResultChecking que puede configurar con los valores NONE, LOG y EXCEPTION que es el caso que nos interesa.

Referencias:

Spring data mongo de documentacion

Ejemplo de configuración

lunes, 18 de junio de 2012

Spring JPA MongoDB: java.lang.NoSuchMethodError: com.mysema.query.mongodb.MongodbQuery

En nuestros proyecto estamos estamos trabajando con Spring Data Jpa para MySql y para MongoDb.

Al hora de hacer una query en MongoDb utilizando QueryDSL para utilizar los predicado que facilitan la creación de queries, nos ha dado el siguiente error.


------
java.lang.NoSuchMethodError: com.mysema.query.mongodb.MongodbQuery.<init>(Lcom/mongodb/DBCollection;Lcom/google/common/base/Function;Lcom/mysema/query/mongodb/MongodbSerializer;)V
 at org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery.<init>(SpringDataMongodbQuery.java:58)
 at org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery.<init>(SpringDataMongodbQuery.java:44)
 at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.createQueryFor(QueryDslMongoRepository.java:167)
 at org.springframework.data.mongodb.repository.support.QueryDslMongoRepository.count(QueryDslMongoRepository.java:154)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
 at java.lang.reflect.Method.invoke(Method.java:601)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.executeMethodOn(RepositoryFactorySupport.java:334)
 at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:319)
 at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
 at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
----- 
El código utilizado el el siguiente:

 
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.querydsl.QueryDslPredicateExecutor;

import charris.example.domain.Product;

public interface ProductRepository extends MongoRepository<Product, String>, QueryDslPredicateExecutor<Product> {

}



El error proviene al hacer una query como esta:

 
ProductRepository repo = context.getBean(ProductRepository.class);
Product p = repo.findOne(QProduct.product.name.eq("ProductName"));



El problema consistía en un problema de versiones entre los jar de
  1. querydl-mongodb
  2. spring-data
El error se solucionó poniendo las versiones  adecuadas de los JAR. Nosotros tuvimos que recurrir a los Nightly builds de spring data para que funcionara perfectamente.

Las versiones de los Jars fueron las siguientes a fecha (18/06/2012)

  •  querydsl-mongodb-2.5.0.jar
  • querydsl-mongodb-2.5.0-apt.jar
  • querydsl-mongodb-2.5.0-apt-one-jar.jar
  • spring-data-commons-core-1.3.2.BUILD-20120618.140513-1.jar
  • spring-data-jpa-1.1.1.BUILD-20120606.192912-1.jar
  • spring-data-mongodb-1.1.0.BUILD-20120614.104654-40.jar

lunes, 30 de abril de 2012

MongoDB Error: java.lang.NoSuchFieldError: socketKeepAlive

Realizando unos tests de Junit con ANT, usando la tarea , sobre la aplicación que estamos creando que utiliza MongoDB nos apareció el siguiente error poco descriptivo:

 

    [junit] Grave: Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@1c81b84] to prepare test instance [com.xxxxx.xxxx.integration.ContentTemplateIntegrationTest@7c329d]
    [junit] java.lang.IllegalStateException: Failed to load ApplicationContext
    [junit]  at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:157)
    [junit]  at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:109)
    [junit]  at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:75)
    [junit]  at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:321)
    [junit]  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:211)
    [junit]  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:288)
    [junit]  at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    [junit]  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:290)
    [junit]  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:231)
    [junit]  at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:47)
    [junit]  at org.junit.runners.ParentRunner$3.run(ParentRunner.java:231)
    [junit]  at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:60)
    [junit]  at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:229)
    [junit]  at org.junit.runners.ParentRunner.access$000(ParentRunner.java:50)
    [junit]  at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:222)
    [junit]  at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    [junit]  at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    [junit]  at org.junit.runners.ParentRunner.run(ParentRunner.java:300)
    [junit]  at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:174)
    [junit]  at junit.framework.JUnit4TestAdapter.run(JUnit4TestAdapter.java:39)
    [junit]  at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.run(JUnitTestRunner.java:518)
    [junit]  at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.launch(JUnitTestRunner.java:1052)
    [junit]  at org.apache.tools.ant.taskdefs.optional.junit.JUnitTestRunner.main(JUnitTestRunner.java:906)
    [junit] Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongo': Cannot create inner bean '(inner bean)' of type [org.springframework.data.mongodb.core.MongoOptionsFactoryBean] while setting bean property 'mongoOptions'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.data.mongodb.core.MongoOptionsFactoryBean]: Constructor threw exception; nested exception is java.lang.NoSuchFieldError: socketKeepAlive
    [junit]  at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:281)
    [junit]  at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:125)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1360)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1118)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:517)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    [junit]  at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:294)
    [junit]  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:225)
    [junit]  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:291)
    [junit]  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
    [junit]  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:567)
    [junit]  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:913)
    [junit]  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:464)
    [junit]  at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:103)
    [junit]  at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:1)
    [junit]  at org.springframework.test.context.support.DelegatingSmartContextLoader.loadContext(DelegatingSmartContextLoader.java:228)
    [junit]  at org.springframework.test.context.TestContext.loadApplicationContext(TestContext.java:124)
    [junit]  at org.springframework.test.context.TestContext.getApplicationContext(TestContext.java:148)
    [junit]  ... 22 more
    [junit] Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)': Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.data.mongodb.core.MongoOptionsFactoryBean]: Constructor threw exception; nested exception is java.lang.NoSuchFieldError: socketKeepAlive
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:997)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:943)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:485)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
    [junit]  at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveInnerBean(BeanDefinitionValueResolver.java:270)
    [junit]  ... 39 more
    [junit] Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.springframework.data.mongodb.core.MongoOptionsFactoryBean]: Constructor threw exception; nested exception is java.lang.NoSuchFieldError: socketKeepAlive
    [junit]  at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:162)
    [junit]  at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:76)
    [junit]  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:990)
    [junit]  ... 43 more
    [junit] Caused by: java.lang.NoSuchFieldError: socketKeepAlive
    [junit]  at org.springframework.data.mongodb.core.MongoOptionsFactoryBean.(MongoOptionsFactoryBean.java:64)
    [junit]  at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    [junit]  at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
    [junit]  at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    [junit]  at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
    [junit]  at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
    [junit]  ... 45 more
    [junit] Tests run: 2, Failures: 0, Errors: 2, Time elapsed: 1,098 sec


Esto se debe a la configuración de la conexión de la aplicación con MongoDB en la que no detecta el parámetro socket-keep-alive
 

 <mongo:options connections-per-host="8"
                    threads-allowed-to-block-for-connection-multiplier="4"
                    connect-timeout="1000"
                    max-wait-time="1500"
                    auto-connect-retry="true"
                    socket-keep-alive="true"
                    socket-timeout="1500"
                    slave-ok="true"
                    write-number="1"
                    write-timeout="0"
                    write-fsync="true"/>

El error es poco descriptivo porque al principio creíamos que era un problema de versiones de los distintas librerías de MongoDB y de Spring Data JPA, pero en realidad el error se produjo porque la librería JAR de conexión de con MongoDB no estaba correctamente importada en el POM del proyecto.

 Una vez importado el jar mongo-2.7.3.jar todo funcionó correctamente y los tests pasaron correctamente.