Translate

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

martes, 24 de julio de 2012

StringTokenizer en Flex

Buscando buscando encontré un código firmado por Alex Ciobanu y que lo tiene en una librería con otras funcionalidades para obtener la misma funcionalidad de Stringtokenizer de Java.

Toda esta funcionalidad está en la librería fxmaker

Como sólo me interesaba la parte del manejo de la cadena miré el código fuente y lo utilicé.

Por si os es útil os dejo la referencia a la librería y al código fuente del StringTokenizer.

Y también os pego el código aquí por si acaso en algún momento desaparece, ya que me parece bastante útil.

--

/**
         *
         * @author Alexutz
         *
         */    
        public class StringTokenizer
        {
                private var tokens : Array;
               
                private var currentToken : int = -1;
                /**
                 *
                 *
                 */            
                public function StringTokenizer(delimiter : String = null, value: String = null){
                        tokenize(value, delimiter);
                }
                /**
                 *
                 * @return
                 *
                 */            
                public function hasMoreTokens() : Boolean{
                        return tokens && currentToken < tokens.length - 1;
                }
                /**
                 *
                 * @return
                 *
                 */            
                public function getToken() : String{
                        if(currentToken >= tokens.length - 1){
                                throw new Error("");
                        }
                        currentToken++;
                        var token : String = tokens[currentToken] as String;
                        return token;
                }
                /**
                 *
                 * @param value
                 * @param delimiter
                 *
                 */            
                public function tokenize(value : String, delimiter : String) : void{
                        if(value == null || delimiter == null){
                                return;
                        }
                        tokens = value.split(delimiter);
                        currentToken = -1;
                }
        }

Software útil: Winsplit Revolution

Este software una vez instalado te permite cambiar la disposición de la ventana activa en la pantalla.

Yo lo encuentro bastante útil porque tengo dos monitores y puedo pasar la ventana de uno a otro con una simple combinación de teclas.

CTRL + ALT + flecha derecha --- pasa la ventana al monitor 2.
CTRL + ALT + flecha izquierda--- pasa la ventana al monitor 1.

CTRL + ALT +  números de teclado numérico --- colocala ventana en una posición relativa al número que pulses.

http://winsplit-revolution.com/home

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