How to prevent implicit caching in Spring Boot 1.5.1 Hibernate

10,190

The problem is your dao, which is flawed in a very dangerous way. A Session isn't thread safe and shouldn't be shared. It should be opened for each action (or at least the current session should be used).

Plain Hibernate Solution

You DAO should look something like this.

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

Now you specific dao should reuse the getSession method.

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

When doing so you probably will run into issues with an error stating that no session can be found due to no transaction (or something along those lines).

To fix this use Spring Boot (and some manual configuration).

First move the hibernate.connection properties to your application.properties and remove them from hibernate.cfg.xml.

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

Now Spring will create a Datasource for you. Next remove your HibernateUtils and configure the SessionFactory using Springs LocalSessionFactoryBean.

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

You will also need the appropriate transaction manager

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

Now due to everything being setup in Spring, the injection of the SessionFactory and the @Transactional you will get a managed Session which will be properly opened and closed for you.

Controller Fix

Your controller is also flawed as you should be injecting a PasswordDao and not the concrete type (which would now fail due the the creation of a proxy for the transactions).

@Controller
public class PasswordsController extends BaseControllerHelper {

    @Autowired
    private PasswordDAO passwordDAO;

JPA Solution

However while all of this will probably work I would strongly suggest to use JPA and the EntityManager instead of the Session and SessionFactory approach.

To do so remove the LocalSessionFactoryBean and the HibernateTransactionManager and add the remaining properties of the hibernate.cfg.xml to the application.properties.

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

Next to the spring.datasource properties that is all you need and you can delete the hibernate.cfg.xml file.

Now instead of using the SessionFactory use the EntityManager in your dao.

public abstract class BaseDao {

    @PersistenceContext
    protected EntityManager em;

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

And your specific dao.

@Repository
@Transactional
public PasswordJpaDao extends BaseDao implements PasswordDao {

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

Spring Data JPA Solution

When using JPA you could even drop your generic dao approach and implementations and use Spring Data JPA instead. Your whole PasswordDao would then look like

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

All the crud functionality (findAll, findOne, save etc.) is available out-of-the-box. Creating queries is pretty easy and saves you from writing the boilerplate code.

Share:
10,190
crmepham
Author by

crmepham

I am currently coding in Java, Kotlin and Go.

Updated on July 21, 2022

Comments

  • crmepham
    crmepham almost 2 years

    I am trying to understand why, after creating a new entity and persisting it whilst the application is running, when retrieving a list of these entities, the new entity should be retrieved from the database, but isn't?

    For example:

    I created a new entity (from the UI) and persisted it successfully like so:

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

    I verified that the entity is persisted in the database.

    Next, when I refresh the UI which lists these entities (for which I added a new one to the database) the new entity is not included and was not retrieved from the following:

    @Repository
    @SuppressWarnings("unchecked")
    public class PasswordDAOHibernate extends BaseDAOHibernate implements PasswordDAO {
    
        @Override
        public Collection<Password> getPasswords() {
    
            Query query = session.createQuery("select ...");
            return query.list();
        }
    

    Here is the interface:

    public interface PasswordDAO {
    
        Password getPassword(Integer id);
    
        Collection<Password> getPasswords();
    
        Collection<Password> getPasswords(PasswordSearchParameters params);
    
        Collection<PasswordType> getPasswordTypes();
    }
    ...
    

    Which is called from a 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);
        }
    

    Here is the configuration currently setup: 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>
    

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

    When I restart the server application the new entity displays in the list.

    Is there some kind of implicit caching going on that I need to clear? Note, I have not implemented any explicit caching at this stage.