【问题标题】:First level cache not working with JPA and Hibernate一级缓存不适用于 JPA 和 Hibernate
【发布时间】:2014-07-03 09:53:24
【问题描述】:

我是一个使用休眠缓存(第一级、第二级和查询缓存)的新手。

我的项目是使用 Spring MVC 和 JPA 配置的。

我正在使用下面的 JUnit 测试用例测试一级缓存。

public class FirstLevelCacheTest
{
    private static final Logger logger = LoggerFactory.getLogger(FirstLevelCacheTest.class);

    @PersistenceContext
    private EntityManager       entityManager;

    @Test
    public void testFirstLevelCacheTest()
    {
        Long clientId = 1L;

        // fetch the client entity from database first time
        Client client1 = entityManager.find(Client.class, clientId);
        logger.debug("Client Name : {}", client1.getName());

        // fetch the client entity again
        client1 = entityManager.find(Client.class, clientId);
        logger.debug("Client Name : {}", client1.getName());
    }
}

而我的实体类定义为:

@Entity
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
@Table(name = "client")
public class Client extends BaseModel
{
    // instance variable declaration

    // getter and setter methods for instance variables
}

如果默认启用一级缓存,这应该执行一次本机查询。 但我在执行此查询时得到以下结果:

Hibernate: select client0_.id as id0_0_, client0_.created as created0_0_, client0_.createdBy as createdBy0_0_, client0_.updated as updated0_0_, client0_.updatedBy as updatedBy0_0_, client0_.contactNo as contactNo0_0_, client0_.contactPerson as contactP7_0_0_, client0_.contactPersonEmail as contactP8_0_0_, client0_.contactPersonNo as contactP9_0_0_, client0_.email as email0_0_, client0_.name as name0_0_ from client client0_ where client0_.id=?
[main] DEBUG Client Name : Client1
Hibernate: select client0_.id as id0_0_, client0_.created as created0_0_, client0_.createdBy as createdBy0_0_, client0_.updated as updated0_0_, client0_.updatedBy as updatedBy0_0_, client0_.contactNo as contactNo0_0_, client0_.contactPerson as contactP7_0_0_, client0_.contactPersonEmail as contactP8_0_0_, client0_.contactPersonNo as contactP9_0_0_, client0_.email as email0_0_, client0_.name as name0_0_ from client client0_ where client0_.id=?
[main] DEBUG Client Name : Client1

下面是我的持久化相关配置:

@Configuration
@EnableTransactionManagement
public class PersistanceConfig
{
    // injection

    private String[] PACKAGES_TO_SCAN = new String[] { "com.mypackage1", "com.mypackage2" };

    @Bean
    public DataSource dataSource()
    {
        // dataSource setting 'com.mysql.jdbc.Driver' as a jdbc driver
    }

    private Properties jpaProperties()
    {
        Properties properties = new Properties();

        properties.put(AvailableSettings.DIALECT, hibernateDialect);
        properties.put(AvailableSettings.SHOW_SQL, hibernateShowSql);

        // 2nd level cache
        properties.put("hibernate.cache.use_second_level_cache", true);
        properties.put("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.EhCacheRegionFactory");

        return properties;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory()
    {
        // entity manager settings using dataSource and jpaProperties
    }

    @Bean
    public JpaTransactionManager transactionManager()
    {
        // transaction manager settings using entityManagerFactory
    }
}

谁能帮我解决一个问题或我做错了什么?

提前致谢!

【问题讨论】:

  • 你能提供你激活缓存的代码片段吗?您的 Client 类上应该有一个 @Cacheable。
  • 另请参阅 oracle 文档的这一部分:docs.oracle.com/javaee/6/tutorial/doc/gkjia.html
  • @AlexandreFILLATRE 请查看有问题的更新。而且我认为我们不需要使用“@Cacheable”注释实体,因为默认情况下第一级缓存是打开的。

标签: java spring hibernate jpa caching


【解决方案1】:

您需要使用@Transactional 注释测试方法或使用Spring TransactionTemplate

即使检索实体不需要事务,将多个调用分组到一个内也会清楚地指定持久性上下文边界(它应该从哪里开始以及应该在哪里结束)。

要复制 1 级缓存,您需要在整个测试方法中使用相同的工作单元,并且使用 @Transactional 注释测试将启动事务(绑定到当前执行的持久性上下文事务的 Spring 逻辑事务,该事务与实际的物理数据库事务)。

当测试方法结束时,事务通常会回滚,因此不会将任何更改传播到任何后续测试,但在当前执行的测试方法中,您会看到自己的更改,即 ACID 中的 Isolation 属性。

【讨论】:

  • 以下是错误的:“JPA 操作总是需要一个正在运行的事务,而不仅仅是持久/合并操作。”。只有在 DB 中更改某些内容的操作才需要事务(例如,不悲观锁定任何行的 SELECT 操作不需要事务)。它还取决于自动提交标志/状态(默认为 false),但这是一个更长的故事(检查this answer)。
  • 我同意@Andrei-i 的观点,即“@Transactional”仅在需要持久化/合并的情况下才需要。
  • @vhad-mihalcea:这很好,测试方法上的“@Transactional”工作正常。但是,如果我为相应的 DAO 创建 getById(java.lang.Long) 怎么办?我应该用 '@Transactional' 注释这个方法吗?如果是的话,如果我需要使用for循环等从Service层调用这个方法的次数怎么办?
  • 我通常让我的服务层设置@Transactional 边界,而不是 DAO 层。这是因为服务最终可能会调用您希望在同一个事务中执行的多个 DAO。这也解决了您的循环 getById 问题,因为在事务中您获得相同的 Hibernate Session,因此无论您调用多少次 session.get() 或 entityManager.find(),一级缓存将始终返回相同的对象实例
【解决方案2】:

我在一个简单的命令行应用程序中测试了您的场景,但无法重现该问题。使用 Hibernate 4.0.1 和 Hibernate 4.3 测试。基本上我在事务之外两次获取相同的实体,并且第二个 SQL 查询没有执行。重要提示:我一个接一个地调用EntityManager.find(),它们之间没有任何其他操作(比如打开一个事务,会触发另一个SQL选择的案例)。

我向您推荐以下内容: 1. 确保您还查看了数据库服务器的日志(以确保 Hibernate 不会记录查询,尽管它不会发送它们)。 2. 将 Hibernate 升级到更新的版本。

另外说明:据我所知,Hibernate 并没有要求使用这样的 Cache。

persistence.xml 我用过:

<persistence version="2.0" xmlns="http://xmlns.jcp.org/xml/ns/persistence"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_0.xsd"> 

    <persistence-unit name="my-PU" transaction-type="RESOURCE_LOCAL">

                <!--<provider>org.hibernate.ejb.HibernatePersistence</provider>-->
                <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

                <class>com.package.Entity</class>
                <exclude-unlisted-classes>false</exclude-unlisted-classes>
                <properties>
                        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
                        <property name="hibernate.hbm2ddl.import_files" valu="sql/import-users.sql"/>
                        <property name="hibernate.show_sql" value="true"/>
                        <property name="hibernate.format_sql" value="false"/>
                </properties>
    </persistence-unit>
</persistence>

【讨论】:

  • 我正在使用最新版本的 Hibernate,即“4.3.5.Final”。
  • 你能发布你的persistence.xml(或者你配置Hibernate的方式)吗?您使用的是什么数据库和驱动程序?
  • 我也用 4.3.5.Final 测试过,结果一样:只有一个查询发送到 DB。
  • 我没有使用persistence.xml,而是进行了基于java的配置。有关更多信息,请检查有问题的更新。我使用 MySQL 作为数据库。
  • 能否请您发布您的配置?
【解决方案3】:

@Transactional 负责处理从 sessionFactory 获取 session 对象和从 entityManagerFactory 获取 entityManger 对象 一级缓存发生在会话级别或实体管理器级别,所以@Tranaction 注释在这里非常重要

【讨论】:

  • 您能解释一下这给出的哪些信息不在接受的答案中吗?
猜你喜欢
  • 2013-11-23
  • 2019-03-19
  • 1970-01-01
  • 1970-01-01
  • 2017-09-29
  • 2017-03-31
  • 2012-06-30
  • 2014-07-10
  • 1970-01-01
相关资源
最近更新 更多