Supuestamente la misma entidad, pero AssertSame falla. datos inconsistentes

Tengo algunos problemas con mi implementación de DAO. Mi escenario: inserto una entidad en mi base de datos, obtengo esta entidad dos veces de mi base de datos. Entiendo la diferencia entre AssertSame y AssertEquals. Supongo que en ambos casos debería pasar la misma entidad. En mi caso, AssertEquals pasa, pero AssertSame falla.

He estado luchando con eso durante mucho tiempo y cualquier ayuda será muy apreciada.

Mi pregunta es: ¿Qué condiciones se deben cumplir para estar seguros de que estas dos entidades son iguales? ¿Qué debo cambiar en mi código?

Pegué solo partes de mis clases y archivos de configuración que, en mi opinión, son esenciales.

La clase StudentEntity se anota con @Entity.

He preparado la siguiente prueba jUnit:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/dao-context.xml", "/hibernate-context.xml", "/core-context.xml" })
public class BidirectionalTest {

    @Autowired
    private UserService userService;

    @Test
    public void testBidirectionalRelation() {
        try {
            StudentEntity s = new StudentEntity();
            s.setLogin("Login");
            userService.registerUser(s);
            StudentEntity foundStudent1 = (StudentEntity) userService.findUserByLogin("Login");
            StudentEntity foundStudent2 = (StudentEntity) userService.findUserByLogin("Login");
            assertEquals(foundStudent1, foundStudent2);
            assertSame(foundStudent1, foundStudent2); // fail!
        } catch (ServiceException e) {
            e.printStackTrace();
        }
    }
}

Parte de la implementación de mi servicio:

@Transactional(propagation=Propagation.REQUIRED)
public class UserServiceImpl implements UserService{

private UserDao userDao;

public AbstractUserEntity findUserByLogin(String login) throws ServiceException {
    Long userId = getUserDao().findUserByLogin(login);
    if(userId == null) {
        return null;
    }
    return getUserDao().findUser(userId);
}

public Long registerUser(AbstractUserEntity user) throws ServiceException {
    Long duplicateUserId = getUserDao().findUserByLogin(user.getLogin());
    if (duplicateUserId!=null) {
        throw new ServiceException("Użytkownik już istnieje");
    }
    return getUserDao().insertUser(user);
}
}

Parte de mi implementación de dao:

@Repository
public class HibernateUserDao implements UserDao {

private EntityManagerFactory emf;

public EntityManagerFactory getEmf() {
    return emf;
}

@PersistenceUnit
public void setEmf(EntityManagerFactory emf) {
    this.emf = emf;
}

@Override
public Long insertUser(AbstractUserEntity user) {
    EntityManager em = emf.createEntityManager();
    try {
        em.getTransaction().begin();
        em.persist(user);
        em.getTransaction().commit();
    } finally {
        if (em != null) em.close();
    }
    return user.getId();
}

@Override
public Long findUserByLogin(String login) {
    EntityManager em = emf.createEntityManager();
    Long result;
    try{
        result = (Long) em.createNamedQuery("findUserByLogin").setParameter("login", login).getSingleResult();          
    } catch(NoResultException nre) {
        return null;
    }
    return result;
}

}

Parte de mi persistencia.xml

<persistence-unit name="JpaPersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
        <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.DefaultNamingStrategy" />
        <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
        <property name="hibernate.hbm2ddl.auto" value="create-drop" />
        <property name="show_sql" value="true" />
    </properties>
</persistence-unit>

Parte de mi core-context.xml

<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="transactionManager" />

<!-- a PlatformTransactionManager is still required -->
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager">
        <ref local="atomikosTM" />
    </property>
    <property name="userTransaction">
        <ref local="atomikosUTX" />
    </property>
</bean>

<bean id="atomikosTM" class="com.atomikos.icatch.jta.UserTransactionManager" />
<bean id="atomikosUTX" class="com.atomikos.icatch.jta.UserTransactionImp" />

editar:

Gracias por sus respuestas. Ahora sé que necesito un caché. Encontré una buena explicación: "En JPA, la identidad del objeto no se mantiene en EntityManagers. Cada EntityManager mantiene su propio contexto de persistencia y su propio estado transaccional de sus objetos". Cambié mi persistence.xml, pero mi prueba aún falla.

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
         version="2.0">
<persistence-unit name="JpaPersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <class>amg.training.spring.model.StudentEntity</class>
    <class>amg.training.spring.model.SubjectEntity</class>
    <class>amg.training.spring.model.TeacherEntity</class>
    <class>amg.training.spring.model.AbstractUserEntity</class>
    <class>amg.training.spring.model.TeacherDegree</class>
    <class>amg.training.spring.dao.AbstractEntity</class>
    <shared-cache-mode>ALL</shared-cache-mode>
    <properties>
        <property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.DefaultNamingStrategy" />
        <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
        <property name="hibernate.hbm2ddl.auto" value="create-drop" />
        <property name="show_sql" value="true" />
        <property name="hibernate.cache.provider_class" value="org.hibernate.cache.EhCacheProvider"/>
        <property name="hibernate.cache.use_second_level_cache" value="true"/>
        <property name="hibernate.cache.use_query_cache" value="true"/>
    </properties>
</persistence-unit>

preguntado el 03 de mayo de 12 a las 13:05

3 Respuestas

Para recuperar el mismo objeto, tendría que usar el caché de consultas de hibernación. Cualquier consulta que vaya a la base de datos creará objetos distintos.

Consulte lo siguiente sobre cómo habilitar la caché de consultas: http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html#performance-querycache

contestado el 03 de mayo de 12 a las 13:05

Gracias, eso es algo nuevo para mí. Traté de configurar el caché en el archivo persistence.xml, pero mi prueba aún falla. - Bartosz Miller

¿Recordaste .setCacheable(true) en tu consulta? - mikepatel

Uso la consulta de hibernación sin .setCallable(). lo puse em.setProperty(QueryHints.HINT_CACHEABLE, true); em.setProperty(QueryHints.HINT_FLUSH_MODE, FlushMode.ALWAYS); em.setProperty(QueryHints.HINT_CACHE_MODE, CacheMode.NORMAL);, pero aún no hay mejoras - Bartosz Miller

La razón por la cual assertSame es que compara objetos, y no sus valores, según la documentación:

afirmar lo mismo (java.lang.Object esperado, java.lang.Object real) - Afirma que dos objetos se refieren al mismo objeto.

Cada recuperación de un estudiante crea un nuevo StudentEntity objeto, aunque se refieren a la misma entrada de la base de datos. Intente almacenar en caché su resultado.

contestado el 03 de mayo de 12 a las 14:05

Gracias. Ahora entiendo que primero findUserByLogin() llega a mi base de datos. El segundo golpea el caché para que la identidad sea la misma. Traté de implementar esto, pero mi prueba aún falla (edité mi publicación). - Bartosz Miller

Finalmente logré hacerlo funcionar. Creo que la falta de caché no fue un problema. Es necesario vincular un recurso (en nuestro caso, EntityManagerFactory) al mismo hilo. enlace al documento del método .bindResource()

@PersistenceUnit
private EntityManagerFactory emf;
private EntityManager em;

@Before
public void setUp() {
    em = emf.createEntityManager();
    TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));
}

@After
public void tearDown() {
TransactionSynchronizationManager.unbindResource(emf);
EntityManagerFactoryUtils.closeEntityManager(em);
}

contestado el 04 de mayo de 12 a las 23:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.