【问题标题】:Why is hibernate session not closed after transaction is completed为什么事务完成后休眠会话没有关闭
【发布时间】:2017-09-22 21:19:38
【问题描述】:

我使用 Spring + hibernate 进行数据库访问 (mysql)。对于只读方法,我需要将底层连接状态更改为只读,以便我可以将读取操作发送到从属(在主从中)。

在下面的代码中,我使用休眠会话将底层连接更改为只读getEmployer 方法。这部分工作正常。 我假设在执行getEmployer 方法后,会话将被关闭(因为它被@Transactional 注释)。所以当调用下面的saveEmployer 方法时,会启动一个新的会话。因此saveEmployer 中的connection.isReadOnly() 应该返回false。但是当我调用saveEmployer 方法时(在调用getEmployer 方法之后),connection.isReadOnly 原来是true

我相信getEmployer 方法执行后会话没有关闭。我的理解是,当方法用@Transactional注解时,事务提交后会话会关闭。我在这里做错了吗?为什么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);
    }
}

这里是根上下文中dataSource的java配置

@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()
    );
}

【问题讨论】:

  • 上升调用者是否也用@Transactionnal注解了?
  • 没有。 getEmployer 和 saveEmployer 的调用者没有用@Transactional 注释
  • 如果您的连接池重用连接并且不重置只读标志,则可以解释。你用的是什么连接池?
  • 我使用hibernate + tomcat + mysql。不确定使用什么连接池。我在帖子中添加了 dataSource 配置。希望这会有所帮助。我还假设从连接池中检索到的连接是随机的?在我的情况下,它似乎总是检索相同的连接(设置为只读)?我写了一个拦截器,在方法执行后重置连接状态。但我担心我可能会做某事。错误的地方可能会导致意想不到的后果(例如,如果在事务提交后会话没有关闭)。
  • 如果您在搞乱状态,您还需要注意自己重置它!。最好使用@Transactional(read-only=true)。这将设置标志并重置它。只有当最外层的 tx 是只读的而不是参与正在进行的交易的 tx 时,它才会这样做。恕我直言,您的设置也存在缺陷,因为执行获取和保存的东西应该在一个服务方法中(看起来您没有服务,而是一个 repo 层并修改它之外的东西)。

标签: mysql spring hibernate transactional


【解决方案1】:

也许使用@Transactional(readonly = true) like explained here 比在本机会话上手动设置这个值更容易。

如果会话将被 Spring 重用取决于(可能是嵌套的)事务划分和/或您的调用在哪个线程中执行。它可以被同一个线程重用(存储在 ThreadLocal 中)。 Here您可以找到更多信息。

【讨论】:

  • 出于某种原因,@Transactional(readonly = true) 对我永远不起作用。互联网上有关于此的相互矛盾的帖子。不知道为什么
  • 如果你有 Transactional(readOnly=true) 的代码正常工作,你能分享你使用的 maven-dependencies 吗?
【解决方案2】:

我知道原因了。在tomcat文档中,它说

池本身插入的唯一状态是 defaultAutoCommit, defaultReadOnly, defaultTransactionIsolation, defaultCatalog 如果这些 被设置。这 4 个属性仅在创建连接时设置。 在使用过程中是否应该修改这些属性 连接,池本身不会重置它们。

发生的情况是连接被重用。但连接池不会重置其状态。所以如果你修改连接状态,你必须重置它,因为连接池不会重置它。

正如其他人所建议的,我在 @Transactional 注释中设置了 readOnly = true,这是一种更简洁的方法。但请注意,org.springframework 的版本必须 >= 4.1。在 4.1 之前,readOnly 只会改变刷新模式,但不会将连接设置为 readOnly。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-03-10
    • 1970-01-01
    • 2011-01-23
    • 1970-01-01
    • 2021-04-04
    • 1970-01-01
    • 1970-01-01
    • 2011-06-13
    相关资源
    最近更新 更多