Hibernate 与 Mybatis 入门使用

in 技术博客 with 0 comment

Hibernate 与 Mybatis 入门使用

1. Hibernate -- 基于 Hibernate 5.3.1 final

Hibernate 核心配置文件 hibernate.cfg.xml

hibernate.cfg.xml 是必备的配置文件,用于配置数据库连接信息(包括我们在 JDBC 中用到的 driver、url、username、password 以及其他的连接配置),以及指定 ORM 配置文件路径。示例如下:

hibernate.cfg.xml:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/lvlang?" +"autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=UTF-8</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">123456</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
        <property name="hibernate.use_sql_comments">true</property>

        <!-- 指定 ORM 配置文件 -->
        <mapping resource="config/User.hbm.xml" />
        <mapping resource="config/Student.hbm.xml" />

    </session-factory>
</hibernate-configuration>

另外,hibernate.cfg.xml 须置于 工程或 classpath 根目录才能让程序找到它。本文测试工程结构如下(使用IntelliJ IDEA):
Hibernate 测试工程结构

ORM 配置文件 ClassName.hbm.xml :

Hibernate 作为一个 ORM (Object Relational Mapping -- 对象--关系型数据库 映射),它使用 ClassName.hbm.xml 配置文件来作为 数据库表 和 Java类的映射配置,本文以两个简单的数据表 (User 和 Student 表)为例,表结构如下:
User 和 Student 表

其中 User 表对应的映射文件 User.hbm.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="hibernate">
    <!-- Table: User -->
    <class name="User" table="User" catalog="lvlang">
        <id name="id" column="id" type="int" >
            <!-- Declaim that the primary key is auto-generated -->
            <generator class="native" />
        </id>
        <property name="name" column="name" length="20" />
        <property name="address" column="address" length="50" />
    </class>
</hibernate-mapping>

<generator class="native" /> 表示该主键元素值由数据库自动生成。

Student 表对应的映射文件 Student.hbm.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="hibernate">
    <!-- Table: Student -->
    <class name="Student"  table="Student" catalog="lvlang">
        <composite-id name="StudentID" class="StudentID">
            <key-property name="id" type="int">
                <column name="id" />
                <!-- Declaim that the primary key is auto-generated -->
            </key-property>
            <key-property name="name" type="java.lang.String" length="20">
                <column name="name" />
            </key-property>
        </composite-id>
        <property name="address" column="address" type="java.lang.String" length="100" />
        <property name="score" column="score" type="int" />
    </class>
</hibernate-mapping>

Hibernate 的 ORM 配置文件一目了然,这是它的一个优势。

映射类 ClassName.java

ORM 中的 数据表有了,映射关系也配置好了,就剩实现对应的 Java 类了:
User.java

/**
 * @ClassName: User.java
 * @Description:
 * @Author: Victor Lv (http://langlv.me)
 * @Email: langlv@qq.com
 * @Date: Jul 2, 2018
 * @Version: V1.0
 */
package hibernate;

/**
 * @ClassName: User.java
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/10 15:44
 * @Version: 1.0
 */

public class User {
    private int id;
    private String name;
    private String address;

    public User() {
    }

    public User(int id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(int id) {
        this.id = id;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the address
     */
    public String getAddress() {
        return address;
    }

    /**
     * @param address the address to set
     */
    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User {id=" + id + ", name=" + name + "}";
    }
}

Student.java,Student 表使用了复合主键,所以它的 primary key 在这里用专用的类封装了:

package hibernate;

/**
 * @ClassName: Student
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/11 11:24
 * @Version: 1.0
 */

public class Student {

    private StudentID studentID;
    private String address;
    private int score;

    public Student() {
    }

    public Student(int id, String name, String address, int score) {
        StudentID ID = new StudentID(id, name);
        this.studentID = ID;
        this.address = address;
        this.score = score;
    }

    public Student(StudentID ID, String address, int score) {
        this.studentID = ID;
        this.address = address;
        this.score = score;
    }

    public StudentID getStudentID() {
        return studentID;
    }

    public void setStudentID(StudentID studentID) {
        this.studentID = studentID;
    }

    public String getAddress() {
        return address;
    }

    public int getScore() {
        return score;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public void setScore(int score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{id = " + getStudentID().getId() + " name = " + getStudentID().getName()
                + " address = " + getAddress() + " score = " + getScore() +" }";
    }
}

对应的 StudentID.java

package hibernate;

import java.io.Serializable;

/**
 * @ClassName: StudentID
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/11 11:16
 * @Version: 1.0
 */

public class StudentID implements Serializable {

    private int id;
    private String name;

    public StudentID(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public StudentID() {
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }
}

SessionFactory——Session 生产工厂

一个 session 就是客户端与服务器的一次会话,SessionFactory 的存在使得 session 的建立变得轻便,好比一壶配好了茶叶的茶,需要时从壶里倒一杯出来即可,请君品鉴,而不是来了一位新客人就得重头开始泡一壶茶。

Hibernate 的SessionFactory 和 Session 管理非常简便,下述 MySessionFactory.java 封装 SessionFactory 的建立和 Session 的获取功能,SessionFactory 仅需一个,所以定义成 static 属性,在类加载时即建立。

MySessionFactory.java:

package hibernate;

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

/**
 * @ClassName: MySessionFactory.java
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/10 15:44
 * @Version: 1.0
 */

public class MySessionFactory {

    private static SessionFactory sessionFactory;

    static {
        //1. find and import the hibernate.cfg.xml configuration
        Configuration config = new Configuration().configure();

        //2. build session factory
        sessionFactory = config.buildSessionFactory();
    }

    public static Session getSession() {
        return sessionFactory.openSession();
    }

}

简单使用--HibernateTest

茶已泡好了一壶,万事胥备,就等客人了。session 的管理尚有 beginTransaction (打开茶壶盖)、 commit (倒茶入杯) 、 close session(合上茶壶盖) 这一系列固定流程,那再雇佣一个服务员来做这一系列的事吧,就叫它 Mysession.java :

MySession.java:

package hibernate;

import org.hibernate.Session;

import java.io.Serializable;

/**
 * @ClassName: MySession
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/10 17:23
 * @Version: 1.0
 */

public class MySession {

    /* 加双重校验锁的懒汉单例模式:
    (参考:https://blog.csdn.net/goodlixueyong/article/details/51935526)

    1、volatile保证了uniqueInstance的修改对各个线程的可见性。

    2、这是个static方法synchronized(this)肯定是不行的,因为没有this(this针对的是实例化对象)。
        再说synchronized(uniqueInstance),synchronized是针对对象而言的,对象都是堆里的对象,
        但是初始化状态下uniqueInstance是null,只是栈里的一个标识,在堆里没有。
        synchronized(null)会报空指针异常。
     */

    private static volatile MySession instance = null;

    public static MySession getInstance() {
        if (null == instance) {
            synchronized (MySession.class) { //synchronized 保证了一次只有一个线程能进入下面的代码块

                /* 双重校验锁:因为上头的 if (null == instance) 无线程锁,
                    假设线程 A 和 B 同时越过了上头的校验,来到了 synchronized 代码块,
                    线程 A 先执行了代码块并 new 了一个 instance ,
                    然后线程 B 进来了,如果此处不加双重校验,那么线程 B 会认为 (null == instance) 仍然是成立的,
                    所以线程 B 也 new 了一个 instance。
                 */
                if (null == instance) {
                    return new MySession();
                }
            }
        }
        return instance;
    }

    private Object execute(Object object, String command, Serializable id) {
        //3. open (create) a session (DB connection)
        Session session = MySessionFactory.getSession();

        //4. begin transaction
        session.beginTransaction();

        //5. execute the task
        Object result = new Object();
        if ("add".equals(command))
            session.save(object);
        else if ("update".equals(command))
            session.update(object);
        else if ("delete".equals(command)) {
            session.delete(object);
        } else if ("load".equals(command))
            result = session.load(object.getClass(), id);
        else if ("get".equals(command))
            result = session.get(object.getClass(), id);

        //6. commit the transaction
        session.getTransaction().commit();

        //7. close session
        session.close();

        //8. close session factory
//        sessionFactory.close();

        return result;
    }

    public void add(Object object) {
        execute(object, "add", null);
    }

    public void update(Object object) {
        execute(object, "update", null);
    }

    public void delete(Object object) {
        execute(object, "delete", null);
    }

    public Object load(Object object, Serializable id) {
        return execute(object, "load", id);
    }

    public Object get(Object object, Serializable id) {
        return execute(object, "get", id);
    }

}

下面就是客人来喝茶了,写了几个简单的 Hibernate 使用样例:

HibernateTest.java:

    package hibernate;

    import org.hibernate.Session;

    import java.io.Serializable;

    /**
     * @ClassName: HibernateTest.java
     * @Description: TODO
     * @Author: http://langlv.me
     * @Date: 2018/7/10 15:44
     * @Version: 1.0
     */

    public class HibernateTest {

        public static void main(String[] args) {
            userTest();
            studentTest();
        }

        public static void userTest() {
            //----- 1. User table ------
            // add one data
            User userAdd = new User(0, "Victor", "Guangdong");
            MySession.getInstance().add(userAdd);

            // query data
            User myUser1 = (User) MySession.getInstance().get(new User(), 1);
            System.out.println(myUser1.toString());

            // update data
            User userUpdate = new User(1, "Bill Gates", "Beijing");
            MySession.getInstance().update(userUpdate);

            // query data
            User myUser2 = (User) MySession.getInstance().get(new User(), 1);
            System.out.println(myUser2.toString());

            // delete one data
            User userDelete = new User(3, null, null);
            MySession.getInstance().delete(userDelete);
        }

        public static void studentTest() {
            //----- 2. Student table ------
            // add data
            Student studentAdd1 = new Student(1, "Bill Gates", "New-York", 100);
            Student studentAdd2 = new Student(2, "Tony", "Beijing", 99);
            Student studentAdd3 = new Student(3, "Victor", "Shanghai", 98);
            MySession.getInstance().add(studentAdd1);
            MySession.getInstance().add(studentAdd2);
            MySession.getInstance().add(studentAdd3);

            //query data
            StudentID ID1 = new StudentID(1, "Bill Gates");
            Student myStudent1 = (Student) MySession.getInstance().get(new Student(), ID1);
            System.out.println((null == myStudent1) ? "Empty" : myStudent1.toString());

            //update data
            Student studentUpdate = new Student(1, "Bill Gates", "Miami", 95);
            MySession.getInstance().update(studentUpdate);

            //query data
            StudentID ID2 = new StudentID(1, "Bill Gates");
            Student myStudent2 = (Student) MySession.getInstance().get(new Student(), ID2);
            System.out.println((null == myStudent2) ? "Empty" : myStudent2.toString());

            //delete data
            Student studentDelete = new Student(2, "Tony", null, 0);
            MySession.getInstance().delete(studentDelete);

            //query data
            StudentID ID3 = new StudentID(2, "Tony");
            Student myStudent3 = (Student) MySession.getInstance().get(new Student(), ID3);
            System.out.println((null == myStudent3) ? "Empty" : myStudent3.toString());
        }

    }

对于入门级别的简单使用,Hibernate 的整个流程下来一气呵成,干练明了,一条 SQL 语句也不用写,只是在使用 Session 的诸如 get、load、save、update、delete 等各种方法时有点小纠结。

2. Mybatis —— 基于 Mybatis-3.4.6

MyBatis 核心配置文件

与 Hibernate 类似,Mybatis 也需要一个配置数据库连接信息的核心配置文件,但它的名字和路径就相对自由,因为后续使用该配置文件的时候需要手工引入。配置如下:

config.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <!-- 引入外部配置文件 -->
    <properties resource="DBConfig.properties"></properties>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${JDBC.driverClass}"/>
                <property name="url" value="${JDBC.url}"/>
                <property name="username" value="${JDBC.userName}"/>
                <property name="password" value="${JDBC.userPassword}"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

这里把数据库的 driverClass、url、username 等信息配置成从外部配置文件 DBConfig.properties 读取:

DBConfig.properties:

JDBC.driverClass=com.mysql.jdbc.Driver
JDBC.url=jdbc:mysql://localhost:3306/lvlang?autoReconnect=true&amp;useUnicode=true&amp;characterEncoding=UTF-8
JDBC.userName=root
JDBC.userPassword=123456

本文的测试工程结构如下(使用 Maven 工程形式):

Mybatis 测试工程结构

ORM 配置文件—— xxxMapper.xml

上述 config.xml 配置指定了一个映射关系配置文件 UserMapper.xml ,配置了 User 表(表结构和上述的例子相同)的SQL操作映射关系:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="UserMapper">

    <select id="selectUser" parameterType="int" resultType="mybatis.User">
        SELECT * FROM User WHERE id = #{id}
    </select>

    <select id="selectUserName" parameterType="int" resultType="mybatis.User">
        SELECT name FROM User WHERE id = #{id}
    </select>

    <insert id="insertUser" parameterType="mybatis.User" useGeneratedKeys="true" keyProperty="id">
        insert into user (name, address)
                VALUES (#{name}, #{address})
    </insert>
    
    <delete id="deleteUser" parameterType="int" >
        DELETE FROM user WHERE id = #{id}
    </delete>

    <update id="updateUser" parameterType="mybatis.User">
        UPDATE user set
            name = #{name},
            address = #{address}
        where id = #{id}
    </update>
</mapper>

与 Hibernate 的映射关系配置文件不一样,Mybatis 的这个配置文件主要配置的是 SQL 语句的 key-value 选项,也就是通过唯一的 key - String,能拉出来对应的 SQL 语句出来执行,同时在该配置文件中指定 SQL 传入参数信息(比如参数类型)以及 resultMap 对应的 POJO 映射。
Hibernate 提供了 SQL 语句层的封装,可以将数据库操作自动生成 SQL 语句(也提供 HQL 的方式编写自定义 SQL),对于简单的数据库操作非常方便;而 Mybatis 则交由程序员自行编写 SQL 语句,灵活性更大。两个框架的这个特性的差异,也引发了网络上就这两个框架的开发效率、性能、可拓展性等展开过热议。

映射类 -- User.java

User.java:


/**
 * @ClassName: User.java
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/10 15:44
 * @Version: 1.0
 */

package mybatis;

public class User {
    private int id;
    private String name;
    private String address;

    public User() {
    }

    public User(int id, String name, String address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(int id) {
        this.id = id;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the address
     */
    public String getAddress() {
        return address;
    }

    /**
     * @param address the address to set
     */
    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name +", address= " + address + " ]";
    }
}

SessionFactory

与 Hibernate 自动读取根目录的 hibernate.cfg.xml 配置来产生 SessionFactory 不同,Mybatis 引入核心配置的方式更多样,比如下述样例,读取指定的配置文件成 InputStream,再用这个 InputStream构造出一个 SessionFactory:

SessionFactory.java:

package mybatis;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * @ClassName: SessionFactory.java
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/10 15:44
 * @Version: 1.0
 */

public class SessionFactory {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        String resource = "config.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSession() {
        return sqlSessionFactory.openSession();
    }

}

Mybatis 简单使用 -- MybatisTest

茶已泡好,请君入席:


package mybatis;

import org.apache.ibatis.session.SqlSession;
import java.io.IOException;

/**
 * @ClassName: MybatisTest.java
 * @Description: TODO
 * @Author: http://langlv.me
 * @Date: 2018/7/10 15:44
 * @Version: 1.0
 */

public class MybatisTest {

    public static void main(String arg[]) throws IOException {
        User myUser1 = selectUserName(7);
        System.out.println(myUser1.toString());

        System.out.println("Insert user: " + insertUser("langlv", "China"));

        System.out.println("Update user: " + updateUser(7, "Bill Gates", "Beijing"));
        User myUser2 = selectUser(7);
        System.out.println(myUser2.toString());

        System.out.println("Delete User: " + deleteUser(12));

    }

    public static User selectUserName(int id) {
        SqlSession session = SessionFactory.getSession();

        System.out.println("Selecting user...");
        try {
            User user = (User) session.selectOne((String) "UserMapper.selectUserName", id);
            if (null != user) {
//                System.out.println(user.toString());
                return user;
            } else {
//                System.out.println("Not found!");
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
            return null;
        } finally {
            session.close();
        }
    }

    public static User selectUser(int id) {
        SqlSession session = SessionFactory.getSession();

        System.out.println("Selecting user...");
        try {
            User user = (User) session.selectOne((String) "UserMapper.selectUser", id);
            if (null != user) {
//                System.out.println(user.toString());
                return user;
            } else {
//                System.out.println("Not found!");
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
            return null;
        } finally {
            session.close();
        }
    }

    public static boolean insertUser(String name, String address) {
        SqlSession session = SessionFactory.getSession();

        System.out.println("Inserting user...");
        try {
            User user1 = new User(0, name, address);
            int index = session.insert("UserMapper.insertUser", user1);
            boolean result = (index > 0) ? true : false;
            session.commit();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
            return false;
        } finally {
            session.close();
        }
    }

    public static boolean updateUser(int id, String name, String address) {
        SqlSession session = SessionFactory.getSession();

        System.out.println("Updating user...");
        try {
            User user = new User(id, name, address);
            int index = session.update("UserMapper.updateUser", user);
            boolean result = (index > 0) ? true : false;
            session.commit();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
            return false;
        } finally {
            session.close();
        }
    }

    public static boolean deleteUser(int id) {
        SqlSession session = SessionFactory.getSession();

        System.out.println("Deleting user...");
        try {
            int index = session.delete("UserMapper.deleteUser", id);
            boolean result = (index > 0) ? true : false;
            session.commit();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            session.rollback();
            return false;
        } finally {
            session.close();
        }
    }
}

与 Hibernate 每次启动数据库操作都得传入整个 POJO 类/对象不同,Mybatis 的理念是,你给我一串 SQL 和传入参数就好,我帮你组装成一条完成的 SQL 拿去 JDBC 运行,Hibernate 则是拿到传入的这个类/对象,自动拼接其成员属性生成 SQL。Hibernate 更省事,Mybatis 更灵活(比如需要写定制化的 SQL)。

结语

本文只简述 Hibernate 和 Mybatis 两个 ORM 框架的入门使用。针对二者的 PK ,网上的各家议论见仁见智。只能说一方面要因地制宜,应场景需求挑选框架;另一方面要得心应手,自己用的顺手顺心,也是挺重要的。

参考文章
Hibernate快速入门
Mybatis 3 简介

Responses