【问题标题】:Hibernate session synchronization - query cache issueHibernate 会话同步 - 查询缓存问题
【发布时间】:2014-11-03 18:37:29
【问题描述】:

我的以下代码面临与休眠会话的同步问题。我的代码中几乎没有并行线程,每个线程都有自己的休眠会话。问题是,由于某些未知原因,其他会话无法感知到某个会话所做的更改。代码位于githubhere

问题:

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

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....

这是我的休眠配置:

<?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>

休眠实用程序

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

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

为了节省您的宝贵时间,请从github repo获取完整代码

【问题讨论】:

    标签: multithreading hibernate session


    【解决方案1】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 2015-05-25
      • 2012-04-29
      • 2012-06-12
      • 2014-06-25
      • 2018-11-18
      • 1970-01-01
      • 1970-01-01
      • 2013-10-31
      • 2014-11-28
      相关资源
      最近更新 更多