java - 如何在 Spring Boot 1.5.1 Hibernate 中防止隐式缓存

标签 java spring hibernate spring-boot

我试图理解为什么在创建新实体并在应用程序运行时持久化它之后,在检索这些实体的列表时,应该从数据库中检索新实体,但不是吗?

例如:

我创建了一个新实体(从 UI)并像这样成功地持久化它:

@Repository
public class BaseDAOHibernate {

    Session session;

    public BaseDAOHibernate() {

        session = HibernateUtils.getSessionFactory().openSession();
    }

    public void save(Object object) {
        Transaction tx = session.beginTransaction();
        session.save(object);
        tx.commit();
    }
...

我已验证该实体已保存在数据库中。

接下来,当我刷新列出这些实体的 UI 时(我为此向数据库添加了一个新实体),新实体不包括在内,也没有从以下内容中检索到:

@Repository
@SuppressWarnings("unchecked")
public class PasswordDAOHibernate extends BaseDAOHibernate implements PasswordDAO {

    @Override
    public Collection<Password> getPasswords() {

        Query query = session.createQuery("select ...");
        return query.list();
    }

这是界面:

public interface PasswordDAO {

    Password getPassword(Integer id);

    Collection<Password> getPasswords();

    Collection<Password> getPasswords(PasswordSearchParameters params);

    Collection<PasswordType> getPasswordTypes();
}
...

从 Controller 调用:

@Controller
public class PasswordsController extends BaseControllerHelper {

    @Autowired
    private PasswordDAOHibernate passwordDAO;

    @RequestMapping("/passwords.htm")
    public void passwords(Map model,
            HttpServletRequest request) {

        Collection<Password> passwords = passwordDAO.getPasswords();
        model.put("passwords", passwords);
    }

这是当前设置的配置: hibernate .cfg.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.bytecode.use_reflection_optimizer">false</property>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.connection.url">jdbc:mysql://host:3306/server</property>
        <property name="hibernate.connection.username">username</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">true</property>
        <mapping class="com.crm.entity.User"></mapping>
        <mapping class="com.crm.entity.Role"></mapping>
        <mapping class="com.crm.entity.Property"></mapping>
        <mapping class="com.crm.entity.Menu"></mapping>
        <mapping class="com.crm.entity.Password"></mapping>
        <mapping class="com.crm.entity.PasswordType"></mapping>
    </session-factory>
</hibernate-configuration>

hibernate 工具.java

@Component
public class HibernateUtils {

    private static final SessionFactory sessionFactory = buildSessionFactory();

    private static SessionFactory buildSessionFactory() {
        try {
            return new Configuration().configure().buildSessionFactory();

        }
        catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static void shutdown() {
        getSessionFactory().close();
    }
}

当我重新启动服务器应用程序时,新实体会显示在列表中。

是否存在某种我需要清除的隐式缓存?请注意,我在此阶段没有实现任何显式缓存。

最佳答案

问题出在您的 dao 上,它存在非常危险的缺陷。 Session 不是线程安全的,不应共享。它应该为每个操作打开(或者至少应该使用当前 session )。

普通 Hibernate 解决方案

你的 DAO 应该看起来像这样。

private final SessionFactory sessionFactory;

protected BaseDAOHibernate(SessionFactory sessionFactory) {
    this.sessionFactory=sessionFactory;
}

protected Session getSession() {
    return this.sessionFactory.getCurrentSession();
}

public void save(Object object) {
    getCurrentSession().save(object);
}

现在您的特定 dao 应该重用 getSession 方法。

@Repository
@Transactional
public class PasswordDAOHibernate extends BaseDao implements PasswordDao {

@Autowired
public PasswordDAOHibernate(SessionFactory sessionFactory) {
    super(sessionFactory);
}

@Override
public Collection<Password> getPasswords() {
    return getSession.query("select ...", Password.class).list();
}

这样做时,您可能会遇到错误,指出由于没有事务无法找到 session (或类似的东西)。

要解决此问题,请使用 Spring Boot(以及一些手动配置)。

首先将 hibernate.connection 属性移动到您的 application.properties 并将它们从 hibernate.cfg.xml 中删除。

spring.datasource.url=jdbc:mysql://host:3306/server
spring.datasource.username=username
spring.datasource.password

现在 Spring 将为您创建一个 Datasource。接下来删除您的 HibernateUtils 并使用 Springs LocalSessionFactoryBean 配置 SessionFactory

@Bean
public LocalSessionFactoryBean sessionFactory(DataSource dataSource) {
    LocalSessionFactoryBean factory = new LocalSessionFactoryBean();
    factory.setDataSource(dataSource);
    return factory;
} 

您还需要合适的事务管理器

@Bean
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
    return new HibernateTransactionManager(sessionFactory);
}

现在,由于在 Spring 中设置了所有内容,SessionFactory@Transactional 的注入(inject),您将获得一个托管的 Session,它将为您正确打开和关闭。

Controller 修复

您的 Controller 也有缺陷,因为您应该注入(inject) PasswordDao 而不是具体类型(由于为交易创建代理,现在会失败)。

@Controller
public class PasswordsController extends BaseControllerHelper {

    @Autowired
    private PasswordDAO passwordDAO;

JPA解决方案

然而,尽管所有这些都可能有效,但我强烈建议使用 JPA 和 EntityManager 而不是 SessionSessionFactory 方法。

为此,删除 LocalSessionFactoryBeanHibernateTransactionManager 并将 hibernate.cfg.xml 的其余属性添加到 应用程序属性

spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=true

spring.datasource 属性旁边就是您所需要的,您可以删除 hibernate.cfg.xml 文件。

现在不要使用 SessionFactory,而是在您的 dao 中使用 EntityManager

public abstract class BaseDao {

    @PersistenceContext
    protected EntityManager em;

    public void save(Object o) {
        em.persist(o);
    }
}

还有你的具体道。

@Repository
@Transactional
public PasswordJpaDao extends BaseDao implements PasswordDao {

@Override
public Collection<Password> getPasswords() {
    return em.createQuery("select ...", Password.class).getResultList();
}

Spring Data JPA解决方案

使用 JPA 时,您甚至可以放弃通用的 dao 方法和实现并使用 Spring Data JPA反而。你的整个 PasswordDao 看起来像

public interface PasswordDao extends JpaRepository<Password, Long> {}

所有 crud 功能(findAllfindOnesave 等)都是开箱即用的。创建查询非常简单,并且可以让您免于编写样板代码。

关于java - 如何在 Spring Boot 1.5.1 Hibernate 中防止隐式缓存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43015713/

相关文章:

hibernate - @UniqueConstraint 检查 JPA 中的多个表

java - Hibernate 不在数据库中保存对象?

访问私有(private)变量的 Java 静态方法

java - Spring中缓存方法最简单/最透明的方式是什么?

java - 不保存子实体

spring - 如何在我的 Spring Cloud Stream 项目中将传入的 header 映射为 String 而不是 byte[]?

spring - "Unable to locate Spring NamespaceHandler for XML schema namespace"仅在可执行 jar 中发生

java.io.IOException : No FileSystem for scheme: abfs for adls-gen 2 in spark java

tomcat - 无法在ubuntu中执行java类文件

java - gradle 构建时出现 StackOverflowError