MyBatis 学习笔记(一)MyBatis的简介与使用以及与其他ORM框架的比较

什么是MyBatis

MyBatis 前身是Apache基金会的开源项目iBatis,在2010年该项目脱离Apache基金会并正式更名为MyBatis,在2013年11月,MyBatis迁移到了GitHub。
MyBatis 是一个轻量级的,半自动的持久化(ORM)框架, 其通过XML映射配置文件或者注解来配置和映射原生类型,接口和Java的POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。之所以是半自动化的框架,是因为其不能像Hibernate一样实现自动生成SQL,其需要用户手动编写SQL语句。方便用户对SQL语句进行优化,适用于大数据量,高并发场景。
MyBatis 是一块比较容易上手的框架,使用者只需要通过简单的学习即可掌握其常用特性。

为什么要使用MyBatis

使用MyBatis访问数据库

首先在pom文件中引入依赖

       <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.5</version>
        </dependency>

正如前面所说MyBatis 是一个半自动持久化框架。所以,需要我们自己来维护sql 语句,编写sql语句的xml文件叫做映射文件。在此处,我建立了一个StudentMapper.xml 文件来维护sql 语句。

<mapper namespace="com.jay.mapper.StudentMapper">
    <resultMap id="BaseColumn" type="com.jay.entity.Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
    </resultMap>
     <select id="selectByName" resultMap="BaseColumn">
        select id,name,age from student where name LIKE '%'#{name}'%'
    </select>
</mapper>

上面的sql语句表示的意思是通过学生名称来模糊匹配学生

public interface StudentMapper {

    /**
     * @param name
     * @return
     */
    List<Student> selectByName(@Param("name") String name);
}

维护完映射文件和对应的接口之后,我们还需要一个XML配置文件来对MyBatis进行一些核心设置,包括获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)以及SQL映射文件的位置信息等。本节所使用的配置如下:

<configuration>
    <!--加载配置文件->jdbc.properties 数据库文件 -->
    <properties resource="jdbc.properties"/>

    <!-- 设置一个默认的连接环境信息 -->
    <environments default="development">

        <!--连接环境信息,取一个任意唯一的名字 -->
        <environment id="development">
            <!-- mybatis使用jdbc事务管理方式 -->
            <transactionManager type="JDBC"/>
            <!-- mybatis使用连接池方式来获取连接 -->
            <dataSource type="POOLED">
                <!-- 配置与数据库交互的4个必要属性 -->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
        
    </environments>

    <!-- 加载映射文件-->
    <mappers>
        <mapper resource="xml/StudentMapper.xml"/>
    </mappers>
</configuration>

到此,MyBatis所需的环境就配置好了,接下来我们将MyBatis跑起来。测试代码如下

    private SqlSessionFactory sqlSessionFactory;
    @Before
    public void setUp() {
        String resource = "chapter1/mybatis-cfg.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream, "development");
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void mybatisTest() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.selectByName("点点");
        if (studentList != null) {
            System.out.println("----->"+studentList.get(0).toString());
        }
        sqlSession.commit();
        sqlSession.close();
    }

如上测试代码,首先,我们根据配置文件得到文件流,然后通过SqlSessionFactoryBuilder工厂类构造器得到SqlSessionFactory,再通过SqlSessionFactory工厂类得到SqlSession。然后根据SqlSession的getMapper()方法得到需要执行的mapper。得到之后调用相应的方法得到结果。
运行结果如下:
在这里插入图片描述

使用JDBC访问数据库

现在我们使用原生的JDBC来操作数据库,主要流程有以下几个:1. 加载数据库驱动,2. 连接数据库,3,通过PreparedStatement执行sql得到ResultSet,4,对ResultSet 进行处理。流程固定。

public class JdbcTest {
    public static void main(String[] args) {
        String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/mybatisdemo";
        String userName = "root";
        String password = "admin";

        Connection conn = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            Class.forName(driver);
            conn = DriverManager.getConnection(url, userName, password);

            String sql = "select id,name,age from student where name LIKE ?";
            statement = conn.prepareStatement(sql);
            statement.setString(1, "%点点%");
            resultSet = statement.executeQuery();
            List<Student> studentList = new ArrayList<Student>();
            if (resultSet != null) {
                while (resultSet.next()) {
                    Student student = new Student();
                    student.setId(resultSet.getInt("id"));
                    student.setName(resultSet.getString("name"));
                    student.setAge(resultSet.getInt("age"));
                    studentList.add(student);
                }
            }
  			System.out.println("----->执行的sql={}"+sql);
            System.out.println("----->resultSet={}"+studentList.get(0).toString());
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            try {
                if (statement != null) {
                    statement.close();
                }
                if (conn != null) {
                    conn.close();

                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

代码比较简单,执行结果如下:
在这里插入图片描述
上面的代码的步骤比较多,但是核心步骤只有两步,分别是执行SQL和处理查询结果。从开发人员的角度来说,我们只关心这两个步骤。原生的JDBC的缺点主要有如下几点:

  1. 每次为了执行某个SQL需要写很多额外的代码,比如加载驱动,创建数据库连接,代码冗余。
  2. 将SQL写在代码中,如果要改动SQL,就需要到代码中进行修改,这样做不合适,因为改动了Java代码就需要重新编译Java文件,在打包发布,同时将SQL和Java代码混在一起,会降低代码的可读性,不利于维护。
  3. 关于执行结果的处理,需要手动的将数据从ResultSet中取出,并设置到我们需要的对象中,如果属性过多,用这种方式处理会非常繁琐。
  4. 每次都要手动管理数据库连接,使用好之后又要手动关闭。

MyBatis VS JDBC

首先我们来看看MyBatis访问数据库的过程

  1. 读取配置文件
  2. 创建SqlSessionFactoryBuilder对象
  3. 通过SqlSessionFactoryBuilder创建SqlSessionFactory对象
  4. 通过SqlSessionFactory创建SqlSession
  5. 为Dao 接口生成代理类
  6. 调用接口方法访问数据库
    需要注意的是,在MyBatis中SqlSessionFactoryBuilder 和 SqlSessionFactory 以及 SqlSession 等对象的作用域和生命周期是不一样的。
    SqlSessionFactoryBuilder
    这个类可以被实例化,使用和丢弃,一旦创建了SqlSessionFactory,就不需要它了,所以,SqlSessionFactoryBuilder实例的最佳作用域是方法作用域(也就是局部方法变量)
    SqlSessionFactory
    SqlSessionFactory一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另外一个实例,使用SqlSessionFactory的最佳实践食杂应用运行期间不要重复创建多起,多次重建SqlSessionFactory被视为一种代码”坏味道“。因此SqlSessionFactory的最佳作用域是应用作用域,有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
    SqlSession
    每个线程都应该有它自己的SqlSession实例,SqlSession的实例不是线程安全的,因此是不能被共享的,所有它的最佳的作用域是请求或方法作用域,绝对不能讲SqlSession实例的引用放在了一个类的静态域,比如Servlet框架中的HttpSession中。
    映射器实例
    映射器是一些由你创建的,绑定你映射的语句的接口,映射器接口的实例是从SqlSession中获得的,因此从技术层面讲,任何映射器实例的最大作用域是请求他们的的SqlSession相同的,尽管如此,映射器实例的最佳作用域是方法作用域。也就是说,映射器实例应该在调用它们的方法中被请求,用过之后即可丢弃。并不需要显示地关闭映射器实例。
    总的来说,MyBatis在易用性上要比JDBC好太多,不过JDBC与MyBatis的目标不同,JDBC是作为一种基础服务,而MyBatis则是构建在基础服务之上的框架。所以JDBC的流程繁琐,从JDBC的角度来说,这里的每一个步骤对于完成数据访问请求来说都是必须的。

使用Spring JDBC访问数据库

Spring JDBC是在JDBC上面做的一层比较薄的封装,主要是为了解决直接使用JDBC的一些痛点,易用性得到了不少的提升。
引入的依赖

    <properties>
        <spring.version>4.3.17.RELEASE</spring.version>
    </properties>
 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>

在使用Spring JDBC之前我们需要做一些配置,我们新建一个配置文件,命名为application.xml,在此配置文件中,我们配置了
数据库的连接信息dataSource,注册了JdbcTemplate实例。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <context:property-placeholder  location="jdbc.properties"/>

    <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <!-- 配置与数据库交互的4个必要属性 -->
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
</beans>

配置完成之后,我们可以写一个测试类来测试一下。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:chapter1/application.xml")
public class SpringJdbcTest {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void  testSpringJdbc() {
        String sql = "select id,name,age from student where name LIKE ?";
        List<Student> studentList = jdbcTemplate.query(sql, new Object[]{"%点点%"}, new BeanPropertyRowMapper<Student>(Student.class));
        System.out.println("----->执行的sql={}"+sql);
        System.out.println("----->查询结果={}"+studentList.get(0).toString());
    }
}

运行结果
在这里插入图片描述
从上面的测试代码我们可以看出,相对于原生JDBC,Spring JDBC 易用性大大提升,注入jdbcTemplate之后,我们就可以通过jdbcTemplate来操作,只关注sql的执行以及结果的处理即可。代码简化了很多。但是SQL语句仍然写在代码中。

使用Hibernate 访问数据库

本节会像之前的章节一样,我会先写代码进行演示,然后在对比Hibernate 和MyBatis的区别。

Hibernate 访问数据库的过程演示

首先,在POM文件中添加所需依赖

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>5.3.2.Final</version>
        </dependency>

接着进行环境配置,主要是关于数据库方面的配置。

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mybatisdemo</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">admin</property>
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="hibernate.show_sql">true</property>
        <mapping resource="chapter1/xml/Student.hbm.xml" />
    </session-factory>
</hibernate-configuration>

环境配置完成之后,我们接着编写映射文件,将表字段与实体类的属性关联起来。如下Student.hbm.xml

<hibernate-mapping package="com.jay.entity">
    <class table="student" name="Student">
        <id name="id" column="id">
            <generator class="native"/>
        </id>
        <property name="name" column="name"/>
        <property name="age" column="age"/>
        <property name="classId" column="class_id"/>
    </class>
</hibernate-mapping>

所有配置完成之后,我们就可以开始编写测试代码进行测试:

public class HibernateTest {
    private SessionFactory sessionFactory;


    @Before
    public void init() {
        Configuration configuration = new Configuration();
        configuration.configure("chapter1/hibernate.cfg.xml");
        sessionFactory = configuration.buildSessionFactory();
    }

    @After
    public void destroy() {
        sessionFactory.close();
    }

    @Test
    public void testORM() {
        System.out.println("--------------ORM Query-------------");

        Session session = null;

        try {
            session = sessionFactory.openSession();
            int id = 1;
            Student student = session.get(Student.class, id);
            System.out.println("ORM Query Result:");
            System.out.println(student.toString());
            System.out.println();
        } finally {
            if (Objects.nonNull(session)) {
                session.close();
            }
        }
    }

    @Test
    public void testHQL() {
        System.out.println("--------------HQL Query-----------");
        Session session = null;

        try {
            session = sessionFactory.openSession();
            String hql = "FROM Student WHERE name=:name";
            Query query = session.createQuery(hql);
            query.setParameter("name", "点点");

            List<Student> studentList = query.list();
            System.out.println("HQL Query Result:");
            studentList.forEach(System.out::println);
            System.out.println();
        } finally {
            if (Objects.nonNull(session)) {
                session.close();
            }
        }
    }

   @Test
    public void testJpaCriteria() {
       System.out.println("-------------JPA Criteria-------------");

       Session session = null;

       try {
           session = sessionFactory.openSession();
           CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
           CriteriaQuery<Student> criteriaQuery = criteriaBuilder.createQuery(Student.class);
//       定义FROM子句
           Root<Student> student = criteriaQuery.from(Student.class);
//       构建查询条件
           Predicate equal = criteriaBuilder.equal(student.get("name"), "点点");
//       通过具有语义化的方法构建SQL,等价于SELECT ... FROM student WHERE ...
           criteriaQuery.select(student).where(equal);

           Query<Student> query = session.createQuery(criteriaQuery);
           List<Student> studentList = query.getResultList();
           System.out.println("JPA Criteria Query Result:");
           studentList.forEach(System.out::println);
       } finally {
           if (Objects.nonNull(session)) {
               session.close();
           }
       }

   }
}

如上代码清单所示,我编写了三个测试用例,第一个直接使用Hibernate生成SQL的功能,如果查询比较简单可以采用此种方式,生成的SQL是

select student0_.id as id1_0_0_, student0_.name as name2_0_0_, student0_.age as age3_0_0_, student0_.class_id as class_id4_0_0_ from student student0_ where student0_.id=?

第二个测试用例,我编写了一条HQL语句,并通过Query来设置参数,同样Hibernate在运行时会将HQL转化成对应的SQL,转化后的SQL如下:

select student0_.id as id1_0_, student0_.name as name2_0_, student0_.age as age3_0_, student0_.class_id as class_id4_0_ from student student0_ where student0_.name=?

第三个测试用例,我们使用JPA Criteria 进行查询,JPA Criteria 具有类型安全,面向对象和语义化的特点,使用JPA Criteria,我们可以用写Java 代码的方式进行数据库操作,无需手写SQL,第三个用例和第二个用例进行的是同样的查询,所以生成的SQL区别不大。
测试代码的运行结果:
在这里插入图片描述

MyBatis VS Hibernate

至此,我们已经对MyBatis和Hibernate访问数据库的过程都做了一次演示,下面我们来对比下MyBatis和Hibernate

  1. MyBatis 需要使用者自行维护SQL,灵活性高,方便对sql进行优化,Hibernate 可以自动生成SQL,使用成本小。
  2. MyBatis 适合于需求变动频繁,业务量的系统,Hibernate 更加适合于变动比较小的系统,比如OA系统

使用Spring Data JPA 访问数据库

首先引入依赖

  <properties>
        <spring.version>4.3.17.RELEASE</spring.version>
    </properties>
 <!--///Spring JPA-->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>1.11.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.12</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.12</version>
        </dependency>

接着添加配置文件application-jpa.xml,主要是配置数据库连接信息,以及事务相关的信息

<!--启用注解配置和包扫描-->
    <context:annotation-config/>
    <context:component-scan base-package="com.jay"/>
    <!--创建Spring Data JPA实例对象-->
    <jpa:repositories base-package="com.jay.chapter1"/>

    <context:property-placeholder  location="jdbc.properties"/>

    <bean id="dataSource"
          class="org.apache.ibatis.datasource.pooled.PooledDataSource">
        <!-- 配置与数据库交互的4个必要属性 -->
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="entityManagerFactory"
          class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="packagesToScan" value="com.jay.entity"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="true"/>
                <property name="showSql" value="true"/>
            </bean>
        </property>
    </bean>
    <!--事务管理器-->
    <bean id="transactionManager"
          class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory"/>
    </bean>
    <!--事务管理-->
    <tx:advice id="transactionAdvice"
               transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="daoPointCut" expression="execution(* com.jay.chapter1.mapper.*.*(..))"/>
        <aop:advisor advice-ref="transactionAdvice" pointcut-ref="daoPointCut"/>
    </aop:config>

配置文件添加完成之后,接着我们编写一个接口继承CrudRepository接口,使其具备基本的增删改查功能。

public interface JpaStudentDao extends CrudRepository<JpaStudent,Integer>{

    /**
     * @param name
     * @return
     */
    List<JpaStudent> getByNameLike(String name);

}

DAO接口添加完成之后,接着我们添加一个测试类进行测试。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:chapter1/application-jpa.xml")
public class JPATest {
    @Autowired
    JpaStudentDao jpaStudentDao;

    @Before
   public void init() {
        JpaStudent jpaStudent = new JpaStudent("张三", 12, "121");
        jpaStudentDao.save(jpaStudent);
    }

    @Test
    public void testCrudRepostitory() {
        List<JpaStudent> jpaStudents = jpaStudentDao.getByNameLike("张三");
        jpaStudents.forEach(System.out::println);
        System.out.println();
    }
}

如上测试类所示,我先使用了JPA自带的save方法向数据库中插入了一条数据,接着自定义了一个查询方法。JPA中查询方法可以由我们声明的命名查询生成,也可以由方法名解析
方法名以find…By, read…By, query…By, count…By和 get…By做开头。在By之前可以添加Distinct表示查找不重复数据。By之后是真正的查询条件。
可以查询某个属性,也可以使用条件进行比较复杂的查询,例如Between, LessThan, GreaterThan, Like,And,Or等。
字符串属性后面可以跟IgnoreCase表示不区分大小写,也可以后跟AllIgnoreCase表示所有属性都不区分大小写。
可以使用OrderBy对结果进行升序或降序排序。
可以查询属性的属性,直接将几个属性连着写即可,如果可能出现歧义属性,可以使用下划线分隔多个属性。
运行结果如下:
在这里插入图片描述

MyBatis VS JPA

通过上面的实例,我们可以了解到JPA的使用,JPA类似于Hibernate都可以自动生成SQL,不同之处是,JPA还可以根据方法名来解析生成sql。MyBatis 还是需要使用者自行维护sql。

总结

本篇文章对MyBatis 是什么,为什么使用,以及与其他ORM框架进行了对比。

参考文献

MyBatis官方文档
MyBatis 源码分析系列文章导读
《MyBatis 技术内幕》- 徐郡明
Spring Data JPA 介绍和使用

源代码

https://github.com/XWxiaowei/MyBatisLearn/tree/master/mybatisDemo




作者:码农飞哥
微信公众号:码农飞哥