Hibernate transaction manager configurations in Sp

2020-08-14 06:40发布

问题:

In my project I use Hibernate with programmatic transaction demarcation. Every time in my Service methods i write something similar to this.

Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
.. perform operation here
session.getTransaction().commit();

Now I'm going to refactor my code with declarative transaction management. What I got now ...

  <context:component-scan base-package="package.*"/>
  
  <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
    <property name="configurationClass">
        <value>org.hibernate.cfg.AnnotationConfiguration</value>
    </property>
  </bean>
  
  <tx:annotation-driven transaction-manager="txManager"/>
  
  
  <bean id="txManager"  class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory">
         <ref local="mySessionFactory"/>
    </property>
  </bean>

Service class:

@Service
public class TransactionalService {

    @Autowired
    private SessionFactory factory;
    
    @Transactional
    public User performSimpleOperation() {
        return (User)factory.getCurrentSession().load(User.class, 1L);
    }
}

And simple test -

@Test
public void useDeclarativeDem() {
    FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("spring-config.xml");
    TransactionalService b = (TransactionalService)ctx.getBean("transactionalService");
     User op = b.performSimpleOperation();
     op.getEmail();

When I try to get user email outside of Transactional method, I got lazy initialization exception, email is my case is a simple string. Hibernate does not even perform sql query, until I call any getters on my POJO.

  1. what I am doing wrong here?

  2. Is this approach valid?

  3. Can you suggest any opensources project which works on spring/hibernate with annotation based configuration?

Update

For some reason if I replace getCurrentSession with openSession this code works fine. Can someone explain it please?

回答1:

Ok, finally i realized what was the problem. In code above i used load instead of get. Session.load did not actually hit the databased. That's the reason why i get lazy-initialization exception outside of @Transactional method

If i use openSession instead of getCurrentSession, session is opened outside of scope spring container. As result session was not close and it allow me to read object properties outside of @Transactional method



回答2:

The reason Hibernate does not perform any SQL queries until you call the getters is because I believe the FetchType is set to LAZY. To fix this problem you will need to change the FetchType to EAGER in your POJO:

@Entity
@Table(name = "user")
public class User {

    /*Other data members*/

    @Basic(fetch = FetchType.EAGER)
    private String email;

}

I personally never had to specify Basic types to have an EAGER FetchType so I'm not entirely sure why your configuration requires this. If its only in your tests it could be due to the way you have your JUnit tests configured. It should have something like this on the class declaration:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/test-app-config.xml"})
@Transactional
public class UserServiceTest {

}

As for a good resource I always find SpringByExample to be helpful.

EDIT

So I'm not entirely sure what is wrong with your configuration. It does differ from the way I have mine set up so here is my typical configuration in hopes that it helps. The hibernate.transaction.factory_class could be a key property you are missing. I also use the AnnotationSessionFactoryBean:

<!-- DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
    p:driverClassName="com.mysql.jdbc.Driver" 
    p:url="jdbc:mysql://localhost/dbname"
    p:username="root"
    p:password="password"/>

<!-- Hibernate session factory -->
<bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
    p:dataSource-ref="dataSource">
    <property name="packagesToScan" value="com.beans"/>
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</prop>
        </props>
    </property>
</bean> 

<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory">
        <ref bean="sessionFactory" />
    </property>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>


回答3:

From Hibernate documentation https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch11.html#objectstate-loading:

Be aware that load() will throw an unrecoverable exception if there is no matching database row. If the class is mapped with a proxy, load() just returns an uninitialized proxy and does not actually hit the database until you invoke a method of the proxy. This is useful if you wish to create an association to an object without actually loading it from the database. It also allows multiple instances to be loaded as a batch if batch-size is defined for the class mapping.

From above documentation in your case bean is mapped with spring proxy so load just returns. Hence you need to use get() instead of load() which always hits the database.