东东东 陈煜东的博客

标签存档: hibernate

Hibernate session在service实现事务,getCurrentSession()和openSession()区别

当我们在使用Hibernate作为数据库操作的类库时,我们一般在DAO层里与数据库相关的操作,把业务逻辑写在service层里。但是如果我们的项目比较小,那么直接在dao层里写事务也是可以的,这个就是看个人了,没有什么特别的规定。但是如果项目比较大,那么DAO应该只做单纯的数据库的操作,service写事务的操作,即整个业务逻辑。

例如:业务逻辑要求向数据库中的用户表增加一个用户,同时向日志表中加入一条日志,而这需要调用DAO的两个方法(UserDao的saveUser和LogDao的saveLog)。这显然是一个事务,也就是如果一个操作出现了问题,就要回滚到初始的状态。那么如何在Service层控制事务呢,本文就以此例的代码说明。

在DAO进行Session事务出现的问题

我们先看看在DAO层里写Hibernate的session的事务。

package com.xxg;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {

    private static final SessionFactory sessionFactory = buildSessionFactory();
    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration().configure().buildSessionFactory();
        }
        catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

创建用户表T_user(id,username)和日志表T_log(id,content),以及它们对应的实体类User、Log及映射文件,这里就不一一贴出代码。

public class UserDao {

    public void saveUser(User user){
        SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //获取SessionFactory
        Session session = sessionFactory.openSession();// openSession
        session.beginTransaction(); //开始事务

        session.save(user);

        session.getTransaction().commit(); //事务提交
        session.close(); //关闭session
    }
}
public class LogDao {

    public void saveLog(Log log){
        SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //获取SessionFactory
        Session session = sessionFactory.openSession();// openSession
        session.beginTransaction(); //开始事务

        session.save(log);

        session.getTransaction().commit(); //事务提交
        session.close(); //关闭session
    }

}

接下来我们看看在service中写一个业务逻辑

public class TestService {

    public void save(User user){
        UserDao userDao = new UserDao();
        userDao.saveUser(user);

        LogDao logDao = new LogDao();
        Log log = new Log();
        log.setContent("插入一个用户");
        logDao.saveLog(log);
    }

}

可以看到,我们在两个DAO里写了数据库的事务,代码中高亮显示了,session.beginTransaction()显示声明事务的开始。

这样写是不对的,因为这两个事情作为一个事务来进行的,会出现一个事务成功提交,而另外一个可能提交失败,导致不一致的情况,这样这两个操作不算是一个事务transaction,所以这么写就是一个失败的事务。

因此,我们要将事务在service中进行声明。

在service层写session的数据库事务

为了将事务放在service中,我们需要更改HibernateUtil的代码才能实现。否则使用上面的那个不能达到我们的需求。在这个新的HibernateUtil代码中,利用了ThreadLocal的线程内的局部变量来保存hibernate的session对象。这样就可以在不同的class中使用同一个session对象,而不用传递参数。

public class HibernateUtil {
    public static final ThreadLocal session = new ThreadLocal();

    public static final SessionFactory sessionFactory;
    static {
        try {
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch ( Throwable ex ) {
            throw new ExceptionInInitializerError( ex );
        }
    }

    public static Session currentSession() throws HibernateException
    {
        Session s = session.get();
        if ( s == null )
        {
            s = sessionFactory.openSession();
            session.set( s );
        }
        return(s);
    }

    public static void closeSession() throws HibernateException
    {
        Session s = session.get();
        if ( s != null )
        {
            s.close();
        }
        session.set( null );
    }
}

接下来,我们将事务放在service中。看代码:

public class TestService {

    public void save(User user){

        SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //获取SessionFactory
        Session session = sessionFactory.getCurrentSession();//getCurrentSession
        session.beginTransaction();//事务开始

        UserDao userDao = new UserDao();
        userDao.saveUser(user);

        LogDao logDao = new LogDao();
        Log log = new Log();
        log.setContent("插入一个用户");
        logDao.saveLog(log);
        session.getTransaction().commit();//事务提交
    }

}

 

public class LogDao {

    public void saveLog(Log log) throws RuntimeException{
        SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //获取SessionFactory
        Session session = sessionFactory.getCurrentSession(); //getCurrentSession

        session.save(log);

        throw new RuntimeException();
    }
}

 

public class UserDao {

    public void saveUser(User user){
        SessionFactory sessionFactory = HibernateUtil.getSessionFactory(); //获取SessionFactory
        Session session = sessionFactory.getCurrentSession();//getCurrentSession

        session.save(user);

    }

}

通过getCurrentSession()可以获得当前线程的session对象,通过它来进行共享session。这样事务就从service开始,然后再service结束。

getCurrentSession()与openSession()区别

getCurrentSession创建的session会和绑定到当前线程,而openSession不会。

getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭

采用getCurrentSession()创建的session会绑定到当前线程中,而采用openSession() 创建的session则不会 * 采用getCurrentSession()创建的session在commit或rollback时会自动关闭,而采用openSession() 创建的session必须手动关闭

2、使用getCurrentSession()需要在hibernate.cfg.xml文件中加入如下配置:

  • 如果使用的是本地事务(jdbc事务) <property name=”hibernate.current_session_context_class”>thread</property>
  • 如果使用的是全局事务(jta事务) <property name=”hibernate.current_session_context_class”>jta</property>

getCurrentSession()与openSession()关联

在 SessionFactory 启动的时候, Hibernate 会根据配置创建相应的 CurrentSessionContext ,在 getCurrentSession() 被调用的时候,实际被执行的方法是 CurrentSessionContext.currentSession() 。在 currentSession() 执行时,如果当前 Session 为空, currentSession 会调用 SessionFactory 的 openSession 。所以 getCurrentSession() 对于 Java EE 来说是更好的获取 Session 的方法。

参考资料

  • http://blog.csdn.net/xiao__gui/article/details/7695698
  • http://blog.csdn.net/loveyout/article/details/4193894

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: Hibernate session在service实现事务,getCurrentSession()和openSession()区别 – https://www.chenyudong.com/archives/hibernate-session-transaction-in-service-not-dao.html

分类: JavaEE

hibernate实体自关联映射

近日在开发系统的一个功能的时候出现问题了,花了6天寻找解决方案。先来看看数据库的category表

数据库category结构
类型
id int(11)
name varchar(255)
parent int(11)

遇见的问题

问题表述:当我从数据库中根据id取出一个category出来的时候,对名字进行更改,然后再保存,就会在数据库中insert一条父记录。此困惑确实不解。还有就是使用junit使用模拟http请求测试也不会出问题使用tomcat就又insert了。

Hibernate对象的3中状态

对于出现的问题有,说保存了一个瞬时对象,不能保存。hibernate object references an unsaved transient instance。关于瞬时态(transien)、游离态(detached、托管态)、持久态(persistent)这里有张图

Hibernate对象的3种状态,瞬时态(transien)、游离态(detached)、持久态(persistent)。通过hibernate的save、update等方法进行转换。 图片来源:http://blog.csdn.net/xiaokaibupabupa/article/details/6785208

有说法是从数据库中取出一个对象,状态变成持久态,在session关闭后,就变成游离态。要想关联的parent目录也想要持久态,需要在hibernate的映射文件中需要使用cascade=”all”或者cascade=”save-update”能使父目录变为持久态。但是我在设添加此设置不管用,还是会想数据库insert一条记录。

我让许多同学来查看代码,都觉得没有什么问题,我就让我同学在自己的电脑上假设一个环境来测试,在他的电脑上也没问题。将同学的配置文件弄到自己的机器上测试,过了许多天,这个问题都没解决。

于是我就从MyEclipse的自带的Hibernate Inverse Engineering功能从数据库导出DAO文件、pojo文件、映射文件。然后将我自己的映射文件按照自动生成的来替换。但是还是不行。

更为烦恼的是,自己使用junit写的测试用例,来模拟http请求,能正确执行,但是通过tomcat来运行就又会出现insert的情况。

不过我在查看控制台的输出的时候,发现取出category进行了一个事务和session,update的时候又进行了一个事务和session。我觉得有点问题,因为这样使用了两个事务,我觉得在Spring的配置事务出现了一些问题。

现在把处理category逻辑放到了service层来进行,本来是在action层,感觉情况有些好转。

hibernate映射文件

把配置文件给贴出来看看,hibernate对象自关联自身配置。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.chenyudong.pojo.Category" >
        <id name="id" column="id" >
            <generator class="identity" ></generator>
        </id>
        <property name="name" column="name" />
        <set name="childCategory" inverse="true" cascade="all"  fetch="join" lazy="false">
            <key column="parentID"></key>
            <one-to-many class="com.chenyudong.pojo.Category" />
        </set>
        <many-to-one name="parent" class="com.chenyudong.pojo.Category"
            column="parentID" ></many-to-one>
    </class>  
</hibernate-mapping>

我们可以看见,在外键的一方,需要使用many-to-one或者其他的one-to-one或者one-to-manymany-to-many配置映射关系。此hibernate的映射关系,相当于数据库中的外键关联。many-to-one说明可以多个目录的parentID相同,指向同一个父目录。而set这个标签,则用于主键一方上,表明指向该记录的集合。

属性inverse描述的是对象之间关联关系的控制方向,也就是由哪个对象来维护他们之间的关联,而属性casade描述的是层级之间的连锁操作方式,也就是一个对象的改变是否也要同步对其管理对象进行相应的操作。

嗯,在耽误了许久的项目后继续赶进度了。

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: hibernate实体自关联映射 – https://www.chenyudong.com/archives/hibernate-entity-self-reference-mapping.html

分类: JavaEE

hibernate mysql 中文乱码异常

用Hibernate往mysql 5.1数据库中进行查询数据,表的结构使用的是utf-8编码,服务器在Linux上,这个时候往数据库上查询,插入没问题。后来往5.1的数据库导入模型的时候,外键不见了,于是在Windows本地安装了一个mysql 5.5的数据库,导入模型正常。但是进行查询的时候抛出异常说

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?? as ??0_, owner0_.??? as ???0_, owner0_.?? as ??0_, owner0_.???? as ????0_ fro' at line 1

但是上面的Hibernate自动生成的sql语句显示不会乱码,当然,这两者没关系,表示我的这个sql语句是没有问题的。

于是我在Windows的mysql5.1中建立一个同样的数据库,发现也是这样的错误。

尝试尝试是否是编码的问题,我检查数据库都是用UTF-8编码设置的,没发现有什么异常。

最后在hibernate.cfg.xml上的session-factory中添加编码设置,最后的文件配置是这样的。

<session-factory>

    <property name="connection.url">jdbc:mysql://localhost:3306/test</property>
    <property name="connection.username">root</property>
    <property name="connection.password">password</property>
    <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="connection.useUnicode">true</property>
    <property name="connection.characterEncoding">UTF-8</property>

    <property name="show_sql">true</property>

    <mapping resource="owner.hbm.xml" />

</session-factory>

声明:未经允许禁止转载 东东东 陈煜东的博客 文章,谢谢。如经授权,转载请注明: 转载自东东东 陈煜东的博客

本文链接地址: hibernate mysql 中文乱码异常 – https://www.chenyudong.com/archives/hibernate-excute-mysql-chinese-character-garbled-exception.html

分类: JavaEE

Copyright © 2017 东东东 陈煜东的博客 粤ICP备13059639号-1

SITEMAP回到顶部 ↑