使用Spring配置Hibernate遇到的JTA问题

spring可以方便的集成Hibernate,用它来管理顶层的JTA,同时使用Hibernate的持久化。在spring很容易划分和声明事物。但问题是:Hibernate有时需要探查当前Current transaction)的事物,这些都需要配置。在应用程序中使用auto flushing致使我们很难探查这些bugs。

Auto flushing
whats that?
它是Hibernate中一个特性,当一个query 被执行,在memory中的数据同步到数据库中。
一般情况下,当session被flushed 后,H 仅仅将会从内存同步到 DB.
看看这段代码
  public void transactionInterceptedMethod() {
    Person p = new Person("Maarten");
    personDao.save(p);
    List<Person> persons = personDao.findAll();
  }

注意:他们是在一个事务中.和所说的一般情况一样
这个new person 是否会在 list中返回了?这取决于spring 和Hibernate的配置。

1.这个对象将不会立刻插入数据库,它保持在内存中,until session is flushed。
2.findAll方法使用的和Insert方法使用的same trasaction。要查到这个new person,至少要在insert transaction执行之前。
3.spring将会在这个方法结束前才会让hibernate提交trasaction。

我们主要针对insert和findAll方法之间的session flushed进行讨论。
设置Hibernate的session为“Auto”,H会检查在内存中是否有改变是否会影响Query,如果有,将会把内存数据同步到数据库中。虽然H的Auto-flushing很自动,来支持查询,但不它不会总是flush,这是对性能的考虑。

有两种可能办法可以解决这些:
1.在Hibernate直接配置
2.通过spring配置Hibernate

使用hibernate.transaction.manager_lookup_class 和hibernate.transaction.factory_class.
虽然他们将被Hibernate用来启动线程,但是我们仅仅打算用它探测他们

通过Spring 注入一个taTransactionManager 到LocalSessionFactoryBean.这依赖于你spring使用的版本,另外设置必须改变。

原理请看原文:
Spring is a great framework for dependency injection and it comes with a lot of support classes and utilities for all kind of things. Hibernate is a persistence service with a lot of useful features, that is relatively easy to use. Configuring both frameworks is not always easy. Configuring them together is sometimes hard and it is easy to make mistakes.

This blog addresses a problem in a configuration that is fairly common: use Spring for transaction management on top of a JTA provider and use Hibernate for persistence. Transaction demarcation is easy and declarative with Spring. The problem is that Hibernate sometimes needs to detect the current transaction and this needs to be configured. This leads to hard to detect bugs in applications that rely on auto flushing.

Auto Flushing
Auto flushing is a Hibernate feature which will synchronize in-memory state with database state when a query is executed. Normally, Hibernate will only synchronize in-memory and database state when the session is flushed. With Spring this can be configured to occur when the transaction is committed. This might lead to unexpected behaviour. Look at the code below


  public void transactionInterceptedMethod() {
    Person p = new Person("Maarten");
    personDao.save(p);
    List<Person> persons = personDao.findAll();
  }

Will the new person be in the list of persons? I think everybody would suspect so. In fact, this relies heavily on how Spring and Hibernate are configured.

The person object will not immediately be inserted into the database when offered to Hibernate. It will be kept in memory, until the session is flushed. (There are exceptions, but this is the general rule).
The findAll method will look in the database in the same transaction as the one that is used to insert the person object. Still it will only find the new person if it has been inserted into the database in the same transaction before the query is made.
Spring will commit the transaction and instruct Hibernate to flush the current session only at the end of the method.
All these circumstances influence the result of the findAll method. It seems that we can only have the new person object in the list if we flush the session between the save and findAll methods.

This is where auto flushing comes in. When the flush mode of the session is set to "AUTO", Hibernate will inspect the in-memory state to see if there are any changes that might influence the result of any query that it is about to execute. If Hibernate determines this is so, it will flush the session, synchronizing the in-memory state and the database state. This will not alter the transactional status, but inly execute some SQL statements within the current transactional context.

Auto-flushing is quite intelligent in what to flush when, although some situations might be hard to detect. To improve performance, Hibernate will not simply always flush everything, but look at the tables in the database that the query touches and the data in-memory that should be stored in those tables. If there is no overlap, no data will be persisted. If an overlap is found, the whole session will be flushed, to ensure consistency.

Spring, JTA and transaction detection
Now this all seems to work out fine, but what does it have to do with Spring, JTA or transaction detection? Well, Hibernate will only perform auto flushing if it detects that there is a transaction running. It is not completely clear to me why it won't do the flushing, but this is how it works. It has a number of strategies for detecting that there is a transaction. The default strategy is to look at its own transaction mechanism.

When using Spring, to manage transactions, using Hibernates mechanism for transactions (which is a thin layer on top of JDBC) is an option. This mechanism does not support distributed transactions. To support distributed transactions, Spring says you can just drop in their JtaTransactionManager to replace the HibernateTransactionManager. This is not completely true though: It will work seamlessly for transaction demarcation, but not for transaction detection through hibernate.

Transaction demarcation will work fine: Spring will start a new Hibernate session whenever a transaction is started. Hibernate will work with that session, oblivious of the transaction that has been opened. This is not a problem for demarcation: Spring will flush and close the session when the transaction is closed.

For Hibernates transaction detection it does not work: Hibernate is oblivious to the Spring started transaction and it will not do auto flushing. There are not many features in Hibernate that depend upon transaction detection, so this misbehavior of the system might go undetected for quite a while. The problem with auto flushing is that it is not always as simple as two consecutive lines doing a save and findAll. The logic that saves an entity and the logic that depends upon the entity being found by a query within the same transaction might be in separate classes, only connected by a typical flow.

Solution
So how do we tell Hibernate what strategy to use for transaction detection? There are two possibilities: 1) configure Hibernate directly or 2) configure Hibernate through Spring.

To configure Hibernate directly, use the hibernate.transaction.manager_lookup_class and hibernate.transaction.factory_class. These properties would be used by Hibernate to start transactions, but we're not starting transactions through Hibernate, just detecting them.

To configure Hibernate through Spring, inject a JtaTransactionManager into the LocalSessionFactoryBean. Depending on the specific version of Spring you are using, additional settings must be changed.

Conclusion
Developing with powerful frameworks like Spring and Hibernate makes many tasks easy. It is of great importance that the frameworks are correctly configured, especially if they need to work together. I hope blog will help you detect a particular small bug in your configuration, that could lead to severe problems.

你可能感兴趣的:(spring,sql,Hibernate,配置管理,performance)