为什么事务完成后休眠会话没有关闭
Why is hibernate session not closed after transaction is completed
我使用 Spring + hibernate 进行数据库访问 (mysql)。对于只读方法,我需要将底层连接状态更改为只读,以便我可以将读取操作发送给从属(在主从中)。
在下面的代码中,我在 getEmployer
方法中使用休眠会话将底层连接更改为只读。这部分工作正常。
我假设在 getEmployer
方法执行后,会话将关闭(因为它被 @Transactional
注释)。所以当调用下面的saveEmployer
方法时,会启动一个新的session。因此saveEmployer
中的connection.isReadOnly()
应该returnfalse
。但是当我调用saveEmployer
方法时(在调用getEmployer
方法之后),connection.isReadOnly
结果是true
。
我相信在 getEmployer
方法执行后会话没有关闭。我的理解是当方法用@Transactional
注解时,事务提交后session会关闭。我在这里做错了什么吗?为什么saveEmployer
方法中的connection.isReadOnly()
是true
?谢谢。
@Service
class public EmployerServiceImpl implements EmployerService{
@Inject EmployerRepository employerRepository;
@PersistenceContext
EntityManager em;
@Transactional
public void getEmployer(long id){
Session session = em.unwrap(Session.class);
session.doWork(new Work(){
@Override
public void execute(Connection connection) throws SQLException{
connection.setReadOnly(true);
}
});
......
this.employerRepository.get(id);
}
@Transactional
public void saveEmployer(){
Session session = em.unwrap(Session.class);
session.doWork(new Work(){
@Override
public void execute(Connection connection) throws SQLException{
log.warn("connection status = " + connection.isReadOnly());
}
});
......
this.employerRepository.save(employer);
}
}
这里是java根上下文中dataSource的配置
@Bean
public DataSource springJpaDataSource()
{
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource("jdbc/SpringJpa");
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean()
{
Map<String, Object> properties = new Hashtable<>();
properties.put("javax.persistence.schema-generation.database.action",
"none");
properties.put("hibernate.search.default.directory_provider",
"filesystem");
properties.put("hibernate.search.default.indexBase", "../searchIndexesApplyJob");
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
LocalContainerEntityManagerFactoryBean factory =
new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(adapter);
factory.setDataSource(this.springJpaDataSource());
factory.setPackagesToScan("com.peer.site.entities", "com.peer.site.message",
"com.peer.site.converters");
factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
factory.setValidationMode(ValidationMode.NONE);
factory.setJpaPropertyMap(properties);
return factory;
}
@Bean
public PlatformTransactionManager jpaTransactionManager()
{
return new JpaTransactionManager(
this.entityManagerFactoryBean().getObject()
);
}
也许使用 @Transactional(readonly = true)
like explained here 比在本机会话中手动设置此值更容易。
并且会话是否会被 Spring 重用取决于(可能是嵌套的)事务划分和/或您的调用在哪个线程中执行。它可以被同一个线程重用(存储在一个 ThreadLocal 中)。 Here您可以找到更多信息。
我知道原因了。在 tomcat 文档中,它说
The only state the pool itself inserts are defaultAutoCommit,
defaultReadOnly, defaultTransactionIsolation, defaultCatalog if these
are set. These 4 properties are only set upon connection creation.
Should these properties be modified during the usage of the
connection, the pool itself will not reset them.
发生的情况是连接被重用。但是连接池不会重置它的状态。所以如果你修改连接状态,你必须重置它,因为连接池不会重置它。
正如其他人所建议的,我在@Transactional 注释中设置了 readOnly = true,这是一种更简洁的方法。但请注意 org.springframework 的版本必须 >= 4.1。 4.1之前,readOnly只会改变flush模式,不会设置连接为readOnly。
我使用 Spring + hibernate 进行数据库访问 (mysql)。对于只读方法,我需要将底层连接状态更改为只读,以便我可以将读取操作发送给从属(在主从中)。
在下面的代码中,我在 getEmployer
方法中使用休眠会话将底层连接更改为只读。这部分工作正常。
我假设在 getEmployer
方法执行后,会话将关闭(因为它被 @Transactional
注释)。所以当调用下面的saveEmployer
方法时,会启动一个新的session。因此saveEmployer
中的connection.isReadOnly()
应该returnfalse
。但是当我调用saveEmployer
方法时(在调用getEmployer
方法之后),connection.isReadOnly
结果是true
。
我相信在 getEmployer
方法执行后会话没有关闭。我的理解是当方法用@Transactional
注解时,事务提交后session会关闭。我在这里做错了什么吗?为什么saveEmployer
方法中的connection.isReadOnly()
是true
?谢谢。
@Service
class public EmployerServiceImpl implements EmployerService{
@Inject EmployerRepository employerRepository;
@PersistenceContext
EntityManager em;
@Transactional
public void getEmployer(long id){
Session session = em.unwrap(Session.class);
session.doWork(new Work(){
@Override
public void execute(Connection connection) throws SQLException{
connection.setReadOnly(true);
}
});
......
this.employerRepository.get(id);
}
@Transactional
public void saveEmployer(){
Session session = em.unwrap(Session.class);
session.doWork(new Work(){
@Override
public void execute(Connection connection) throws SQLException{
log.warn("connection status = " + connection.isReadOnly());
}
});
......
this.employerRepository.save(employer);
}
}
这里是java根上下文中dataSource的配置
@Bean
public DataSource springJpaDataSource()
{
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource("jdbc/SpringJpa");
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean()
{
Map<String, Object> properties = new Hashtable<>();
properties.put("javax.persistence.schema-generation.database.action",
"none");
properties.put("hibernate.search.default.directory_provider",
"filesystem");
properties.put("hibernate.search.default.indexBase", "../searchIndexesApplyJob");
HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setDatabasePlatform("org.hibernate.dialect.MySQL5InnoDBDialect");
LocalContainerEntityManagerFactoryBean factory =
new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(adapter);
factory.setDataSource(this.springJpaDataSource());
factory.setPackagesToScan("com.peer.site.entities", "com.peer.site.message",
"com.peer.site.converters");
factory.setSharedCacheMode(SharedCacheMode.ENABLE_SELECTIVE);
factory.setValidationMode(ValidationMode.NONE);
factory.setJpaPropertyMap(properties);
return factory;
}
@Bean
public PlatformTransactionManager jpaTransactionManager()
{
return new JpaTransactionManager(
this.entityManagerFactoryBean().getObject()
);
}
也许使用 @Transactional(readonly = true)
like explained here 比在本机会话中手动设置此值更容易。
并且会话是否会被 Spring 重用取决于(可能是嵌套的)事务划分和/或您的调用在哪个线程中执行。它可以被同一个线程重用(存储在一个 ThreadLocal 中)。 Here您可以找到更多信息。
我知道原因了。在 tomcat 文档中,它说
The only state the pool itself inserts are defaultAutoCommit, defaultReadOnly, defaultTransactionIsolation, defaultCatalog if these are set. These 4 properties are only set upon connection creation. Should these properties be modified during the usage of the connection, the pool itself will not reset them.
发生的情况是连接被重用。但是连接池不会重置它的状态。所以如果你修改连接状态,你必须重置它,因为连接池不会重置它。
正如其他人所建议的,我在@Transactional 注释中设置了 readOnly = true,这是一种更简洁的方法。但请注意 org.springframework 的版本必须 >= 4.1。 4.1之前,readOnly只会改变flush模式,不会设置连接为readOnly。