Hibernate会话同步 - 查询缓存问题

时间:2014-11-03 18:37:29

标签: multithreading hibernate session

我的以下代码正面临着与hibernate会话的同步问题。我的代码中几乎没有并行线程,每个线程都拥有自己的hibernate会话。问题是,一个会话所做的更改由于某些未知原因而未被其他人感知。该代码位于github here

问题:

我在这里用三个主题解释它:PRODUCER,CONSUMER_1,CONSUMER_2。 CONSUMER_1等待生产者完成其工作,即使在那之后,最终,它没有看到PRODUCER线程所做的更改。为什么会这样?

package org.example.hibernate;

import org.example.hibernate.model.User;
import org.example.hibernate.util.HibernateUtil;

import java.util.Random;

public class Main {

    /**
     * This object acts as synchronisation semaphore between threads.
     * (Note : aware that wait within hibernate session is discouraged)
     * Here it is used to show that the consumer tries to read/get after
     * producer has successfully completed the transaction.
     * So here, the producer notifies waiting threads with this object
     */
    public static final Object LOCK = new Object();

    /**
     * user Id is primary key, a random int is suffixed to preserve uniqueness
     * Here, Producer saves an Object of this ID, then consumer tries to read it
     */
    private static final String USER_ID = "user-" + new Random().nextInt(10000);

    /**
     * This is producer thread, it inserts a record and notifies about it to
     * other waiting threads.
     */
    private static Thread PRODUCER = new Thread("producer") {
        // this this creates a user and notifies threads waiting for some event
        @Override
        public void run() {
            HibernateUtil.getInstance().executeInSession(new Runnable() {
                @Override
                public void run() {
                    User user = new User();
                    user.setId(USER_ID);
                    user.setName("name-" + USER_ID);
                    user.save();
                }
            });
            // outside the session
            synchronized (LOCK) {
                print("Notifying all consumers");
                LOCK.notifyAll();
            }
            print("dying...");
        }
    };

    /**
     * This thread tries to read first, if it misses, then waits for the producer to
     * notify, after it receives notification it tries to read again
     */
    private static Thread CONSUMER_1 = new Thread("consumer_one"){
        // this thread checks if data available(user with specific ID),
        // if not available, waits for the the producer to notify it

        @Override
        public void run() {
            HibernateUtil.getInstance().executeInSession(new Runnable() {
                @Override
                public void run() {
                    try {
                        User readUser = User.getById(USER_ID);
                        if(readUser == null) {                  // data not available
                            synchronized (LOCK) {
                                print("Data not available, Waiting for the producer...");
                                LOCK.wait();               // wait for the producer
                                print("Data available");
                            }
                            print("waiting for some more time....");
                            Thread.sleep(2 * 1000);
                            print("Enough of waiting... now going to read");
                        }
                        readUser = User.getById(USER_ID);
                        if(readUser == null) {
                            // why does this happen??
                            throw new IllegalStateException(
                                    Thread.currentThread().getName()
                                            + " : This shouldn't be happening!!");
                        } else {
                            print("SUCCESS: Read user :" + readUser);
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            print("dying...");
        }
    };

    /**
     *   this thread waits for the the producer to notify it, then tries to read
     */
    private static Thread CONSUMER_2 = new Thread("consumer_two"){

        @Override
        public void run() {
            HibernateUtil.getInstance().executeInSession(new Runnable() {
                @Override
                public void run() {
                    try {
                        synchronized (LOCK) {
                            print("Data not available, Waiting for the producer...");
                            LOCK.wait();                                      // wait for the producer notification
                            print("Data available");
                        }
                        print("waiting for some more time....");
                        Thread.sleep(2 * 1000);
                        print("Enough of waiting... now going to read");
                        User readUser = User.getById(USER_ID);
                        if(readUser == null) {
                            throw new IllegalStateException(
                                    Thread.currentThread().getName() +
                                            " : This shouldn't be happening!!");
                        } else {
                            print("SUCCESS :: Read user :" + readUser);
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            print("dying...");
        }
    };


    /**
     * Just another print method to include time stamp and thread name
     * @param msg
     */
    public static void print(String msg) {
        System.out.println(Thread.currentThread().getName() + " : "
                + System.currentTimeMillis()+ " : "+ msg);
    }


    public static void main(String[] args) throws InterruptedException {

        // Initialise hibernate in main thread
        HibernateUtil.getInstance();

        PRODUCER.start();
        CONSUMER_1.start();
        CONSUMER_2.start();

        PRODUCER.join();
        CONSUMER_1.join();
        CONSUMER_2.join();
        print("Exiting....");
    }
}

输出:

    INFO: HHH000232: Schema update complete
[main] INFO org.example.hibernate.util.HibernateUtil - Hibernate Initialised..
consumer_two : 1415036718712 : Data not available, Waiting for the producer...
[producer] INFO org.example.hibernate.util.HibernateUtil - Starting the transaction...
[consumer_two] INFO org.example.hibernate.util.HibernateUtil - Starting the transaction...
[consumer_one] INFO org.example.hibernate.util.HibernateUtil - Starting the transaction...
consumer_one : 1415036718831 : Data not available, Waiting for the producer...
[producer] INFO org.example.hibernate.util.HibernateUtil - Committing the transaction...
producer : 1415036718919 : Notifying all consumers
producer : 1415036718919 : dying...
consumer_one : 1415036718919 : Data available
consumer_one : 1415036718919 : waiting for some more time....
consumer_two : 1415036718919 : Data available
consumer_two : 1415036718919 : waiting for some more time....
[producer] INFO org.example.hibernate.util.HibernateUtil - Session was closed...
consumer_one : 1415036720919 : Enough of waiting... now going to read
consumer_two : 1415036720920 : Enough of waiting... now going to read
Nov 03, 2014 11:15:20 PM com.mchange.v2.c3p0.stmt.GooGooStatementCache assimilateNewCheckedOutStatement
INFO: Multiply prepared statement! select user0_.id as id1_0_0_, user0_.name as name2_0_0_ from user user0_ where user0_.id=?
java.lang.IllegalStateException: consumer_one : This shouldn't be happening!!
    at org.example.hibernate.Main$2$1.run(Main.java:79)
    at org.example.hibernate.util.HibernateUtil.executeInSession(HibernateUtil.java:60)
    at org.example.hibernate.Main$2.run(Main.java:61)
[consumer_one] INFO org.example.hibernate.util.HibernateUtil - Committing the transaction...
[consumer_one] INFO org.example.hibernate.util.HibernateUtil - Session was closed...
consumer_one : 1415036720931 : dying...
consumer_two : 1415036720940 : SUCCESS :: Read user :User{id='user-422', name='name-user-422'} org.example.hibernate.model.User@4666d804
consumer_two : 1415036720943 : dying...
[consumer_two] INFO org.example.hibernate.util.HibernateUtil - Committing the transaction...
[consumer_two] INFO org.example.hibernate.util.HibernateUtil - Session was closed...
main : 1415036720943 : Exiting....

这是我的hibernate配置:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration SYSTEM "classpath://org/hibernate/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
    <property name="hibernate.hbm2ddl.auto">update</property>
    <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="connection.url">jdbc:mysql://localhost:3306/hib_ex</property>
    <property name="connection.username">hibuser</property>
    <property name="connection.password">hibpass</property>

    <!-- JDBC connection pool (use the built-in) -->
    <property name="connection.pool_size">10</property>
    <property name="hibernate.c3p0.min_size">5</property>
    <property name="hibernate.c3p0.max_size">20</property>
    <property name="hibernate.c3p0.timeout">1800</property>
    <property name="hibernate.c3p0.max_statements">50</property>
    <property name="connection.provider_class"> org.hibernate.connection.C3P0ConnectionProvider</property>

    <property name="hibernate.cache.use_second_level_cache">false</property>
    <property name="hibernate.cache.use_query_cache">false</property>
    <property name="hibernate.cache.use_minimal_puts">true</property>

    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="hibernate.current_session_context_class">thread</property>
    <property name="hibernate.cache.provider_class">org.hibernate.cache.internal.NoCachingRegionFactory</property>
    <property name="show_sql">false</property>

    <mapping class="org.example.hibernate.model.User" />

</session-factory>

hibernate实用程序

public enum HibernateUtil {
    INSTANCE;

    private final Logger LOG = LoggerFactory.getLogger(HibernateUtil.class);
    private final String CONFIG_FILE = "hibernate.xml";

    private final SessionFactory sessionFactory;

    HibernateUtil(){
        LOG.info("Initialising hibernate...");
        URL configUrl = getClass().getClassLoader().getResource(CONFIG_FILE);
        final Configuration configuration = new Configuration();
        try {
            configuration.configure(configUrl);
            ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
                    .applySettings(configuration.getProperties())
                    .build();
            sessionFactory = configuration.buildSessionFactory(serviceRegistry);

            LOG.info("Hibernate Initialised..");
        } catch (Exception e){
            throw new IllegalStateException("Could not init hibernate!");
        }
    }

    public Session getSession(){
        if(sessionFactory.getCurrentSession() != null
                && sessionFactory.getCurrentSession().isOpen()) {
            return sessionFactory.getCurrentSession();
        } else {
            LOG.info("Opening a session");
            return sessionFactory.openSession();
        }
    }

    public void executeInSession(Runnable runnable){
        Session session = getSession();
        Transaction transaction = session.getTransaction();
        if(!transaction.isActive()){
            LOG.info("Starting the transaction...");
            transaction.begin();
        }
        try {
            runnable.run();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            if(transaction.isActive()) {
                LOG.info("Committing the transaction...");
                transaction.commit();
            } else {
                LOG.info("Transaction was committed...");
            }
            if(session.isOpen()){
                LOG.info("Closing the session...");
                session.close();
            } else {
                LOG.info("Session was closed...");
            }
        }
    }

    public static HibernateUtil getInstance(){
        return INSTANCE;
    }
}

请帮助我理解:
- 即使在PRODUCER线程的事务成功完成后,为什么CONSUMER_1线程的User.getById(userId)会返回null
- 当CONSUMER_1变为空时,CONSUMER_2线程的User.getById(userId)几乎可以同时获取同一个对象?

为了节省宝贵的时间,请从github repo

获取完整的代码

1 个答案:

答案 0 :(得分:2)

我的猜测是你的数据库事务隔离保证了可重复的读取。由于使用者1首先读取实体并发现它为null,然后在同一事务中执行相同的查询,因此返回相同的结果:null。事务是孤立运行的,即 I ACID 。您的交易应尽可能短。当您发现预期的实体不可用时,您不应该让事务和会话打开。所以,而不是做

open session and transaction
    get entity
    wait for entity to be available
    get entity again
close the transaction and session

你应该做

open session and transaction
    get entity
close the transaction and session
wait for entity to be available
open session and transaction
    get entity again
close the transaction and session