MyBatis 学习笔记(全)

MyBatis 学习笔记(全)

Mybatis官方文档是学习Mybatis最好的入门资料:Mybatis 中文文档

参考文章链接:mybatis学习笔记 、 聊聊MyBatis缓存机制 、 MyBatis原理深入解析(一)

本文在上面引用中的几篇文章(mybatis学习笔记、聊聊MyBatis缓存机制、MyBatis原理深入解析(一))的基础上进行整理、合并,修改一部分内容,加入了部分自己的理解。

感谢原作者的付出

文章很长,对 Mybatis 的最常用的基础部分做了总结和概括,基本不涉及源码和深入的实现原理。

(1)-对原生jdbc程序中的问题总结

本文总结jdbc编程的一般步骤,总结这样编程存在的问题,并附上典型地jdbc示例demo

jdbc编程步骤

  1. 加载数据库驱动
  2. 创建并获取数据库链接
  3. 创建jdbc statement对象
  4. 设置sql语句
  5. 设置sql语句中的参数(使用preparedStatement)
  6. 通过statement执行sql并获取结果
  7. 对sql执行结果进行解析处理
  8. 释放资源(resultSet、preparedstatement、connection)

问题总结

1.数据库连接,使用时就创建,不使用立即释放,对数据库进行频繁连接开启和关闭,造成数据库资源浪费,影响数据库性能。

设想:使用数据库连接池管理数据库连接。

2.将sql语句硬编码到java代码中,如果sql语句修改,需要重新编译java代码,不利于系统维护。

设想:将sql语句配置在xml配置文件中,即使sql变化,不需要对java代码进行重新编译。

3.向preparedStatement中设置参数,对占位符号位置和设置参数值,硬编码在java代码中,不利于系统维护。

设想:将sql语句及占位符号和参数全部配置在xml中。

4.从resutSet中遍历结果集数据时,存在硬编码,将获取表的字段进行硬编码,不利于系统维护。

设想:将查询的结果集,自动映射成java对象。

参考代码

package com.iot.mybatis.jdbc;

//import java.sql.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Created by Administrator on 2016/2/21.
 */
public class JdbcTest {
    public static void main(String[] args) {
        //数据库连接
        Connection connection = null;
        //预编译的Statement,使用预编译的Statement提高数据库性能
        PreparedStatement preparedStatement = null;
        //结果集
        ResultSet resultSet = null;

        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");

            //通过驱动管理类获取数据库链接
            connection =  DriverManager.getConnection("jdbc:mysql://120.25.162.238:3306/mybatis001?characterEncoding=utf-8", "root", "123");
            //定义sql语句 ?表示占位符
            String sql = "select * from user where username = ?";
            //获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            //设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "王五");
            //向数据库发出sql执行查询,查询出结果集
            resultSet =  preparedStatement.executeQuery();
            //遍历查询结果集
            while(resultSet.next()){
                System.out.println(resultSet.getString("id")+"  "+resultSet.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            //释放资源
            if(resultSet!=null){
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(preparedStatement!=null){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(connection!=null){
                try {
                    connection.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

        }

    }

}

JDBC 向 Mybatis 的转化

JDBC 存在的问题以及相应的解决方案

1. 数据库的连接获取和释放

  • 数据库连接频繁的开启和关闭本身就造成了资源的浪费,影响系统的性能

    数据库连接的获取和关闭我们可以使用数据库连接池来解决资源浪费的问题。通过连接池就可以反复利用已经建立的连接去访问数据库了。减少连接的开启和关闭的时间。

  • 但是现在连接池多种多样,可能存在变化,有可能采用DBCP的连接池,也有可能采用容器本身的JNDI数据库连接池

    我们可以通过DataSource进行隔离解耦,我们统一从DataSource里面获取数据库连接,DataSource具体由DBCP实现还是由容器的JNDI实现都可以,所以我们将DataSource的具体实现通过让用户配置来应对变化

2. SQL 的统一存取

  • 我们使用JDBC进行操作数据库时,SQL语句基本都散落在各个JAVA类中,这样有三个不足之处:

    1. 可读性很差,不利于维护以及做性能调优。
    2. 改动Java代码需要重新编译、打包部署。
    3. 不利于取出SQL在数据库客户端执行(取出后还得删掉中间的Java代码,编写好的SQL语句写好后还得通过+号在Java进行拼凑)。

    我们可以考虑不把SQL语句写到Java代码中,那么把SQL语句放到哪里呢?首先需要有一个统一存放的地方,我们可以将这些SQL语句统一集中放到配置文件或者数据库里面(以key-value的格式存放)。然后通过SQL语句的key值去获取对应的SQL语句。

    既然我们将SQL语句都统一放在配置文件或者数据库中,那么这里就涉及一个SQL语句的加载问题

3. 传入参数映射和动态SQL

  • 很多情况下,我们都可以通过在SQL语句中设置占位符来达到使用传入参数的目的,这种方式本身就有一定局限性,它是按照一定顺序传入参数的,要与占位符一一匹配。但是,如果我们传入的参数是不确定的(比如列表查询,根据用户填写的查询条件不同,传入查询的参数也是不同的,有时是一个参数、有时可能是三个参数),那么我们就得在后台代码中自己根据请求的传入参数去拼凑相应的SQL语句,这样的话还是避免不了在Java代码里面写SQL语句的命运。既然我们已经把SQL语句统一存放在配置文件或者数据库中了,怎么做到能够根据前台传入参数的不同,动态生成对应的SQL语句呢?

    1. 我们先解决这个动态问题,按照我们正常的程序员思维是,通过if和else这类的判断来进行是最直观的,这个时候我们想到了JSTL中的 这样的标签,那么,能不能将这类的标签引入到SQL语句中呢?假设可以,那么我们这里就需要一个专门的SQL解析器来解析这样的SQL语句,但是,if判断的变量来自于哪里呢?传入的值本身是可变的,那么我们得为这个值定义一个不变的变量名称,而且这个变量名称必须和对应的值要有对应关系,可以通过这个变量名称找到对应的值,这个时候我们想到了key-value的Map。解析的时候根据变量名的具体值来判断。
    2. 假如前面可以判断没有问题,那么假如判断的结果是true,那么就需要输出的标签里面的SQL片段,但是怎么解决在标签里面使用变量名称的问题呢?这里我们需要使用一种有别于SQL的语法来嵌入变量(比如使用#变量名#)。这样,SQL语句经过解析后就可以动态的生成符合上下文的SQL语句。
    3. 怎么区分开占位符变量和非占位变量?有时候我们单单使用占位符是满足不了的,占位符只能为查询条件占位,SQL语句其他地方使用不了。这里我们可以使用#变量名#表示占位符变量,使用变量名表示非占位符变量

4. 结果映射和结果缓存

  • 执行SQL语句、获取执行结果、对执行结果进行转换处理、释放相关资源是一整套下来的。假如是执行查询语句,那么执行SQL语句后,返回的是一个ResultSet结果集,这个时候我们就需要将ResultSet对象的数据取出来,不然等到释放资源时就取不到这些结果信息了。我们从前面的优化来看,以及将获取连接、设置传入参数、执行SQL语句、释放资源这些都封装起来了,只剩下结果处理这块还没有进行封装,如果能封装起来,每个数据库操作都不用自己写那么一大堆Java代码,直接调用一个封装的方法就可以搞定了。

    我们分析一下,一般对执行结果的有哪些处理,有可能将结果不做任何处理就直接返回,也有可能将结果转换成一个JavaBean对象返回、一个Map返回、一个List返回等`,结果处理可能是多种多样的。从这里看,我们必须告诉SQL处理器两点:第一,需要返回什么类型的对象;第二,需要返回的对象的数据结构怎么跟执行的结果映射,这样才能将具体的值copy到对应的数据结构上。

    接下来,我们可以进而考虑对SQL执行结果的缓存来提升性能。缓存数据都是key-value的格式,那么这个key怎么来呢?怎么保证唯一呢?即使同一条SQL语句几次访问的过程中由于传入参数的不同,得到的执行SQL语句也是不同的。那么缓存起来的时候是多对。但是SQL语句和传入参数两部分合起来可以作为数据缓存的key值

5. 解决重复SQL语句问题

  • 由于我们将所有SQL语句都放到配置文件中,这个时候会遇到一个SQL重复的问题,几个功能的SQL语句其实都差不多,有些可能是SELECT后面那段不同、有些可能是WHERE语句不同。有时候表结构改了,那么我们就需要改多个地方,不利于维护

    当我们的代码程序出现重复代码时怎么办?将重复的代码抽离出来成为独立的一个类,然后在各个需要使用的地方进行引用。对于SQL重复的问题,我们也可以采用这种方式,通过将SQL片段模块化,将重复的SQL片段独立成一个SQL块,然后在各个SQL语句引用重复的SQL块,这样需要修改时只需要修改一处即可

(2)-mybatis概述

本文对mybatis做一个简单介绍,包括框架原理,执行过程,开发方法,输入输出映射以及动态 sql 会在后续的系列文章中详细说明

mybatis 介绍

mybatis是一个持久层的框架,是apache下的顶级项目。

mybatis让程序将主要精力放在sql上,通过mybatis提供的映射方式,自由灵活生成(半自动化,大部分需要程序员编写sql)满足需要sql语句。

mybatis可以将向 preparedStatement中的输入参数自动进行输入映射,将查询结果集灵活映射成java对象。(输出映射)

框架原理

mybatis框架结构图:

MyBatis 学习笔记(全)_第1张图片

mybatis框架执行过程

MyBatis 学习笔记(全)_第2张图片

1、配置mybatis的配置文件,SqlMapConfig.xml(名称不固定)

2、通过配置文件,加载mybatis运行环境,创建SqlSessionFactory会话工厂(SqlSessionFactory在实际使用时按单例方式)

3、通过SqlSessionFactory创建SqlSession。SqlSession是一个面向用户接口(提供操作数据库方法),实现对象是线程不安全的,建议sqlSession应用场合在方法体内。

4、调用sqlSession的方法去操作数据。如果需要提交事务,需要执行SqlSession的commit()方法。

5、释放资源,关闭SqlSession

mybatis开发dao的方法

1.原始dao 的方法

  • 需要程序员编写dao接口和实现类
  • 需要在dao实现类中注入一个SqlSessionFactory工厂

2.mapper代理开发方法(建议使用)

只需要程序员编写mapper接口(就是dao接口)。
程序员在编写mapper.xml(映射文件)和mapper.java需要遵循一个开发规范:

  • mapper.xml中namespace就是mapper.java的类全路径。
  • mapper.xml中statement的id和mapper.java中方法名一致。
  • mapper.xml中statement的parameterType指定输入参数的类型和mapper.java的方法输入参数类型一致
  • mapper.xml中statement的resultType指定输出结果的类型和mapper.java的方法返回值类型一致。

SqlMapConfig.xml配置文件:可以配置properties属性、别名、mapper加载。

输入映射和输出映射

  • 输入映射:
    • parameterType:指定输入参数类型可以简单类型、pojo、hashmap。
    • 对于综合查询,建议parameterType使用包装的pojo,有利于系统扩展。
  • 输出映射:
    • resultType:查询到的列名和resultType指定的pojo的属性名一致,才能映射成功。
    • reusltMap:可以通过resultMap 完成一些高级映射。如果查询到的列名和映射的pojo的属性名不一致时,通过resultMap设置列名和属性名之间的对应关系(映射关系)。可以完成映射。
      • 高级映射:
        将关联查询的列映射到一个pojo属性中。(一对一)
        将关联查询的列映射到一个List中。(一对多)

动态sql

  • 动态sql:(重点)
    • if判断(掌握)
    • where
    • foreach
    • sql片段(掌握)

mybatis和hibernate本质区别和应用场景

  • hibernate

是一个标准ORM框架(对象关系映射)。入门门槛较高的,不需要程序写sql,sql语句自动生成了。对sql语句进行优化、修改比较困难的。

应用场景:适用与需求变化不多的中小型项目,比如:后台管理系统,erp、orm、oa。。

  • mybatis

专注是sql本身,需要程序员自己编写sql语句,sql修改、优化比较方便。mybatis是一个不完全的ORM框架,虽然程序员自己写sql,mybatis也可以实现映射(输入映射、输出映射)。

应用场景:适用与需求变化较多的项目,比如:互联网项目。

(3)-入门程序

入门程序(一)

仅仅使用 Mybatis 进行简单的查询

工程结构

  • SqlMapConfig.xml


<configuration>
    
    <environments default="development">
        <environment id="development">
            
            <transactionManager type="JDBC" />
            
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://120.25.162.238:3306/mybatis001?characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="123" />
            dataSource>
        environment>
    environments>

configuration>

映射文件

  • sqlmap/User.xml



<mapper namespace="test">
    
    
    
    <select id="findUserById" parameterType="int" resultType="com.iot.mybatis.po.User">
        SELECT * FROM  user  WHERE id=#{value}
    select>

    
    <select id="findUserByName" parameterType="java.lang.String" resultType="com.iot.mybatis.po.User">
        SELECT * FROM user WHERE username LIKE '%${value}%'
    select>


mapper>

在sqlMapConfig.xml中加载User.xml


<mappers>
    <mapper resource="sqlmap/User.xml"/>
mappers>

程序代码

  • pojo:User.java
package com.iot.mybatis.po;

import java.util.Date;

/**
 * Created by Administrator on 2016/2/21.
 */
public class User {
    //属性名要和数据库表的字段对应
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
    
    // 省略 set/get 方法

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

  • 测试代码
package com.iot.mybatis.first;

import com.iot.mybatis.po.User;
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 org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * Created by Administrator on 2016/2/23.
 */
public class MybatisFirst {

    //根据id查询用户信息,得到一条记录结果

    @Test
    public void findUserByIdTest() throws IOException{
        // mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream =  Resources.getResourceAsStream(resource);
        //创建会话工厂,传入mybatis配置文件的信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        // 通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 通过SqlSession操作数据库
        // 第一个参数:映射文件中statement的id,等于=namespace+"."+statement的id
        // 第二个参数:指定和映射文件中所匹配的parameterType类型的参数
        // sqlSession.selectOne结果 是与映射文件中所匹配的resultType类型的对象
        // selectOne查询出一条记录
        User user = sqlSession.selectOne("test.findUserById", 1);

        System.out.println(user);

        // 释放资源
        sqlSession.close();

    }

    // 根据用户名称模糊查询用户列表
    @Test
    public void findUserByNameTest() throws IOException {
        // mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // 创建会话工厂,传入mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);

        // 通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // list中的user和映射文件中resultType所指定的类型一致
        List<User> list = sqlSession.selectList("test.findUserByName", "小明");
        System.out.println(list);
        sqlSession.close();

    }


}

输出:

  • findUserByIdTest()
...
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
User [id=1, username=王五, sex=2, birthday=null, address=null]
...
  • findUserByNameTest()
...
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE username LIKE '%小明%' 
DEBUG [main] - ==> Parameters: 
DEBUG [main] - <==      Total: 3
[User [id=16, username=张小明, sex=1, birthday=null, address=河南郑州], User [id=22, username=陈小明, sex=1, birthday=null, address=河南郑州], User [id=25, username=陈小明, sex=1, birthday=null, address=河南郑州]]
...

总结

  • parameterType

在映射文件中通过parameterType指定输入参数的类型

  • resultType

在映射文件中通过resultType指定输出结果的类型

  • #{}${}

#{}表示一个占位符号;

${}表示一个拼接符号,会引起sql注入,所以不建议使用

  • selectOneselectList

selectOne表示查询一条记录进行映射,使用selectList也可以使用,只不过只有一个对象

selectList表示查询出一个列表(参数记录)进行映射,不能够使用selectOne查,不然会报下面的错:

org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3

入门程序二

添加、删除、更新用户

映射文件

  • User.xml,在入门程序一基础上增加
    
    <insert id="insertUser" parameterType="com.iot.mybatis.po.User">
        
        <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
          SELECT LAST_INSERT_ID()
        selectKey>
        INSERT INTO user (username,birthday,sex,address)values (#{username},#{birthday},#{sex},#{address})
        
        

    insert>

    
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id=#{id}
    delete>

    
    <update id="updateUser" parameterType="com.iot.mybatis.po.User">
        update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
        where id=#{id}
    update>

(注:这里的birthday字段在mysql表中是DATE类型,在User类中birthday属性是java的java.util.Date类型,并没有进行转换就插入成功了。

看到有的文章说,在字段中有Date和DateTime类型,在插入数据时只要将实体的属性设置成Timestamp就会对应mysql的DateTime类型,Date会对应mysql的Date类型:
#{modified_date,jdbcType=TIMESTAMP}、#{date,jdbcType=DATE}

我上面的birthday,配置成#{birthday,jdbcType=TIMESTAMP},结果也插入成功了,具体实现待查)

程序代码

  • User.java,在入门程序一基础上增加三个测试方法
  // 添加用户信息
    @Test
    public void insertUserTest() throws IOException {
        // mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // 创建会话工厂,传入mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);

        // 通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 插入用户对象
        User user = new User();
        user.setUsername("王小军");
        user.setBirthday(new Date());
        user.setSex("1");
        user.setAddress("河南郑州");

        sqlSession.insert("test.insertUser", user);

        // 提交事务
        sqlSession.commit();

        // 获取用户信息主键
        System.out.println(user.getId());
        // 关闭会话
        sqlSession.close();

    }

    // 根据id删除 用户信息
    @Test
    public void deleteUserTest() throws IOException {
        // mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // 创建会话工厂,传入mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);

        // 通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 传入id删除 用户
        sqlSession.delete("test.deleteUser", 29);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();

    }

    // 更新用户信息
    @Test
    public void updateUserTest() throws IOException {
        // mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // 创建会话工厂,传入mybatis的配置文件信息
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);

        // 通过工厂得到SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 更新用户信息

        User user = new User();
        //必须设置id
        user.setId(27);
        user.setUsername("王大军");
        user.setBirthday(new Date());
        user.setSex("2");
        user.setAddress("河南郑州");

        sqlSession.update("test.updateUser", user);

        // 提交事务
        sqlSession.commit();

        // 关闭会话
        sqlSession.close();

    }

  • 自增主键返回
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
          SELECT LAST_INSERT_ID()
selectKey>

如果没有在上面的配置中配置resultType,则会报下面的异常

org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'test.insertUser!selectKey'.  It's likely that neither a Result Type nor a Result Map was specified.
### The error may exist in sqlmap/User.xml
### The error may involve test.insertUser!selectKey-Inline
### The error occurred while setting parameters
### SQL: SELECT LAST_INSERT_ID()
### Cause: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'test.insertUser!selectKey'.  It's likely that neither a Result Type nor a Result Map was specified.

	...

Caused by: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'test.insertUser!selectKey'.  It's likely that neither a Result Type nor a Result Map was specified.


总结

  • #{}${}

#{}表示一个占位符号,#{}接收输入参数,类型可以是简单类型,pojo、hashmap。

如果接收简单类型,#{}中可以写成value或其它名称。

#{}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值。

${}表示一个拼接符号,会引用sql注入,所以不建议使用${}

${}接收输入参数,类型可以是简单类型,pojo、hashmap。

如果接收简单类型,${}中只能写成value。

${}接收pojo对象值,通过OGNL读取对象中的属性值,通过属性.属性.属性…的方式获取对象属性值。

(4)-开发dao方法

本文讲解SqlSession,并对两种方法( 原始dao开发和mapper代理开发 )分别做简单展示

SqlSession使用范围

  • SqlSessionFactoryBuilder

通过SqlSessionFactoryBuilder创建会话工厂SqlSessionFactorySqlSessionFactoryBuilder当成一个工具类使用即可,不需要使用单例管理SqlSessionFactoryBuilder。在需要创建SqlSessionFactory时候,只需要new一次SqlSessionFactoryBuilder即可。

  • SqlSessionFactory

通过SqlSessionFactory创建SqlSession,使用单例模式管理sqlSessionFactory(工厂一旦创建,使用一个实例)。将来mybatis和spring整合后,使用单例模式管理sqlSessionFactory

  • SqlSession

SqlSession是一个面向用户(程序员)的接口。SqlSession中提供了很多操作数据库的方法:如:selectOne(返回单个对象)、selectList(返回单个或多个对象)。

SqlSession是线程不安全的,在SqlSesion实现类中除了有接口中的方法(操作数据库的方法)还有数据域属性。

SqlSession最佳应用场合在方法体内,定义成局部变量使用。

原始dao开发方法

程序员需要写dao接口和dao实现类

  • dao接口
public interface UserDao {
    //根据id查询用户信息
    public User findUserById(int id) throws Exception;

    //根据用户名列查询用户列表
    public List<User> findUserByName(String name) throws Exception;

    //添加用户信息
    public void insertUser(User user) throws Exception;

    //删除用户信息
    public void deleteUser(int id) throws Exception;
}
  • dao接口实现类
package com.iot.mybatis.dao;

import com.iot.mybatis.po.User;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.util.List;

/**
 * Created by Brian on 2016/2/24.
 */
public class UserDaoImpl implements UserDao{
    // 需要向dao实现类中注入SqlSessionFactory
    // 这里通过构造方法注入
    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImpl(SqlSessionFactory sqlSessionFactory){
        this.sqlSessionFactory = sqlSessionFactory;
    }



    @Override
    public User findUserById(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("test.findUserById",id);
        //释放资源
        sqlSession.close();
        return user;
    }

    @Override
    public List<User> findUserByName(String name) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        List<User> list = sqlSession.selectList("test.findUserByName", name);

        // 释放资源
        sqlSession.close();

        return list;
    }

    @Override
    public void insertUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //执行插入操作
        sqlSession.insert("test.insertUser", user);

        // 提交事务
        sqlSession.commit();

        // 释放资源
        sqlSession.close();
    }

    @Override
    public void deleteUser(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //执行插入操作
        sqlSession.delete("test.deleteUser", id);

        // 提交事务
        sqlSession.commit();

        // 释放资源
        sqlSession.close();
    }
}

测试代码

package com.iot.mybatis.dao;

import java.io.InputStream;

import com.iot.mybatis.po.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;



public class UserDaoImplTest {

	private SqlSessionFactory sqlSessionFactory;

	// 此方法是在执行testFindUserById之前执行
	@Before
	public void setUp() throws Exception {
		// 创建sqlSessionFactory

		// mybatis配置文件
		String resource = "SqlMapConfig.xml";
		// 得到配置文件流
		InputStream inputStream = Resources.getResourceAsStream(resource);

		// 创建会话工厂,传入mybatis的配置文件信息
		sqlSessionFactory = new SqlSessionFactoryBuilder()
				.build(inputStream);
	}

	@Test
	public void testFindUserById() throws Exception {
		// 创建UserDao的对象
		UserDao userDao = new UserDaoImpl(sqlSessionFactory);

		// 调用UserDao的方法
		User user = userDao.findUserById(1);
		
		System.out.println(user);
	}

}

  • 总结原始dao开发问题
  1. dao接口实现类方法中存在大量模板方法,设想能否将这些代码提取出来,大大减轻程序员的工作量。
  2. 调用sqlsession方法时将statement的id硬编码了
  3. 调用sqlsession方法时传入的变量,由于sqlsession方法使用泛型,即使变量类型传入错误,在编译阶段也不报错,不利于程序员开发。

mapper代理方法

程序员需要编写

  • mapper 接口(相当于dao接口)
  • mapper.xml 映射文件

程序员编写mapper接口需要遵循一些开发规范,mybatis可以自动生成mapper接口实现类代理对象。

开发规范

  • 在mapper.xml中namespace等于mapper接口地址

    namespace 命名空间,作用就是对sql进行分类化管理,理解为sql隔离
    使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口地址

  • mapper.java接口中的方法名和mapper.xml中statement的id一致

  • mapper.java接口中的方法输入参数类型和mapper.xml中statement的parameterType指定的类型一致。

  • mapper.java接口中的方法返回值类型和mapper.xml中statement的resultType指定的类型一致。

<select id="findUserById" parameterType="int" resultType="com.iot.mybatis.po.User">
    SELECT * FROM  user  WHERE id=#{value}
select>
//根据id查询用户信息
public User findUserById(int id) throws Exception;

总结:以上开发规范主要是对下边的代码进行统一生成:

User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.insert("test.insertUser", user);

代码

  • mapper.xml



<mapper namespace="com.iot.mybatis.mapper.UserMapper">
 
    
    
    <select id="findUserById" parameterType="int" resultType="com.iot.mybatis.po.User">
        SELECT * FROM  user  WHERE id=#{value}
    select>

    
    <select id="findUserByName" parameterType="java.lang.String" resultType="com.iot.mybatis.po.User">
        SELECT * FROM user WHERE username LIKE '%${value}%'
    select>


    
    <insert id="insertUser" parameterType="com.iot.mybatis.po.User">
        
        <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
          SELECT LAST_INSERT_ID()
        selectKey>
        INSERT INTO user (username,birthday,sex,address)values (#{username},#{birthday},#{sex},#{address})
        
        

    insert>

    
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from user where id=#{id}
    delete>

    
    <update id="updateUser" parameterType="com.iot.mybatis.po.User">
        update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
        where id=#{id}
    update>
    
mapper>
  • 在SqlMapConfig.xml中加载映射文件
<mappers>  
    <mapper resource="mapper/UserMapper.xml"/>  
mappers>  
  • UserMapper.java
public interface UserMapper {
    //根据id查询用户信息
    public User findUserById(int id) throws Exception;

    //根据用户名列查询用户列表
    public List<User> findUserByName(String name) throws Exception;

    //添加用户信息
    public void insertUser(User user) throws Exception;

    //删除用户信息
    public void deleteUser(int id) throws Exception;

    //更新用户
    public void updateUser(User user)throws Exception;
}
  • UserMapperTest/java
public class UserMapperTest {  
  
  
    private SqlSessionFactory sqlSessionFactory;  
      
    //注解Before是在执行本类所有测试方法之前先调用这个方法  
    @Before  
    public void setup() throws Exception{  
        //创建SqlSessionFactory  
        String resource="SqlMapConfig.xml";  
          
        //将配置文件加载成流  
        InputStream inputStream = Resources.getResourceAsStream(resource);  
        //创建会话工厂,传入mybatis配置文件的信息  
        sqlSessionFactory=new SqlSessionFactoryBuilder().build(inputStream);  
    }  
      
    @Test  
    public void testFindUserById() throws Exception{  
          
        SqlSession sqlSession=sqlSessionFactory.openSession();  
          
        //创建UserMapper代理对象  
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);  
          
        //调用userMapper的方法  
        User user=userMapper.findUserById(1);  
          
        System.out.println(user.getUsername());  
    }  
}  

一些问题总结

  • 代理对象内部调用selectOneselectList

    • 如果mapper方法返回单个pojo对象(非集合对象),代理对象内部通过selectOne查询数据库。
    • 如果mapper方法返回集合对象,代理对象内部通过selectList查询数据库。
  • mapper接口方法参数只能有一个是否影响系统开发

mapper接口方法参数只能有一个,系统是否不利于扩展维护?系统框架中,dao层的代码是被业务层公用的。即使mapper接口只有一个参数,可以使用包装类型的pojo满足不同的业务方法的需求。

注意:持久层方法的参数可以包装类型、map…等,service方法中建议不要使用包装类型(不利于业务层的可扩展)。

(5)-配置文件

本文主要讲解SqlMapConfig配置文件

参考mybatis – MyBatis 3 | Configuration

SqlMapConfig.xml中配置的内容和顺序如下

  • properties(属性)
  • settings(全局配置参数)
  • typeAliases(类型别名)
  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
  • environments(环境集合属性对象)
    • environment(环境子属性对象)
      • transactionManager(事务管理)
      • dataSource(数据源)
  • mappers(映射器)

(注:粗体是重点,斜体不常用)

properties(属性)

将数据库连接参数单独配置在db.properties中,只需要在SqlMapConfig.xml中加载db.properties的属性值。在SqlMapConfig.xml中就不需要对数据库连接参数硬编码。

将数据库连接参数只配置在db.properties中。原因:方便对参数进行统一管理,其它xml可以引用该db.properties。

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://120.25.162.238:3306/mybatis001?characterEncoding=utf-8
jdbc.username=root
jdbc.password=123

在sqlMapConfig.xml加载属性文件:

<properties resource="db.properties">
        
        
properties>


<environments default="development">
    <environment id="development">
        
        <transactionManager type="JDBC" />
        
        <dataSource type="POOLED">
            <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>

注意: MyBatis 将按照下面的顺序(优先级)来加载属性:

  • properties元素体内定义的属性首先被读取。
  • 然后会读取properties元素中resource或url加载的属性,它会覆盖已读取的同名属性。
  • 最后读取parameterType传递的属性,它会覆盖已读取的同名属性。

建议:

  • 不要在properties元素体内添加任何属性值,只将属性值定义在properties文件中。
  • 在properties文件中定义属性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX

settings(全局参数配置)

mybatis框架在运行时可以调整一些运行参数,比如:开启二级缓存、开启延迟加载…

全局参数将会影响mybatis的运行行为。具体参考官网:

mybatis-settings

typeAliases(类型别名)

在mapper.xml中,定义很多的statement,statement需要parameterType指定输入参数的类型、需要resultType指定输出结果的映射类型。

如果在指定类型时输入类型全路径,不方便进行开发,可以针对parameterTyperesultType指定的类型定义一些别名,在mapper.xml中通过别名定义,方便开发。

  • mybatis默认支持别名

参考 typeAliases

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
  • 自定义别名
    • 单个别名定义
    • 批量定义别名(常用)

<typeAliases>

    
    
    
    <package name="com.iot.mybatis.po"/>

typeAliases>

typeHandlers(类型处理器)

mybatis中通过typeHandlers完成jdbc类型和java类型的转换。例如:

<select id="findUserById" parameterType="int" resultType="user">
		select * from user where id = #{id}
select>

mybatis自带的类型处理器基本上满足日常需求,不需要单独定义。

mybatis支持类型处理器

参考 typeHandlers

类型处理器 Java类型 JDBC类型
BooleanTypeHandler Boolean,boolean 任何兼容的布尔值
ByteTypeHandler Byte,byte 任何兼容的数字或字节类型
ShortTypeHandler Short,short 任何兼容的数字或短整型
IntegerTypeHandler Integer,int 任何兼容的数字和整型
LongTypeHandler Long,long 任何兼容的数字或长整型(后面还有很多)

mappers(映射配置)

  • 通过resource加载单个映射文件

<mapper resource="mapper/UserMapper.xml"/>
  • 通过mapper接口加载单个mapper
 
<mapper class="com.iot.mybatis.mapper.UserMapper"/> 

目录示例

com.iot.mybatis.mapper------------------package包
           |----UserMapper.java
           |----UserMapper.xml
              
  • 批量加载mapper(推荐使用)

<package name="com.iot.mybatis.mapper"/>

sql片段(重点)

将上边实现的动态sql判断代码块抽取出来,组成一个sql片段。其它的statement中就可以引用sql片段。

  • 定义sql片段

<sql id="query_user_where">
    <if test="userCustom!=null">
        <if test="userCustom.sex!=null and userCustom.sex!=''">
            AND user.sex = #{userCustom.sex}
        if>
        <if test="userCustom.username!=null and userCustom.username!=''">
            AND user.username LIKE '%${userCustom.username}%'
        if>
    if>
sql>
  • 引用sql片段

<select id="findUserList" parameterType="com.iot.mybatis.po.UserQueryVo"
        resultType="com.iot.mybatis.po.UserCustom">
    SELECT * FROM user
    
    <where>
        
        <include refid="query_user_where">include>
        
    where>
select>

(6)-输入映射

本文主要讲解mybatis的输入映射。

通过parameterType指定输入参数的类型,类型可以是

  • 基本数据类型:包含int,String,Date等。基本数据类型作为传参,只能传入一个。通过#{参数名} 即可获取传入的值
  • 复杂数据类型:包含pojo 、Map。通过#{属性名}或#{map的KeyName}即可获取传入的值

基本数据类型示例

  • mapper.xml
  <select id="selectTeacher" parameterType="int" resultType="com.myapp.domain.Teacher">
  	select * from Teacher where c_id=#{id}
  select>

因为 java 反射只能获取方法参数的类型,但无从得知方法参数的名字的,所以这里的”参数名“可以是任意的。

Map 类型传入示例

在基本类型中,我们只能传入一个参数。 如果要传入多个参数怎么办?比如要查询某个价格区间的商品,那么就需要构建minPrice、maxPrice两个参数,这时可以用到Map。 通过**#{map的KeyName}**即可获取传入的值。

  • mapper.xml
<select id="searchByPrice" parameterType="Map" resultType="Product">
  select * from Product where price >= #{minPrice} and price <= #{maxPrice} 
select>
    
/** 
Map 的定义
Map<String, Double> pricemap=new HashMap<String,Double>();
pricemap.put("minPrice", 1000.00);
pricemap.put("maxPrice", 6000.00);
 */ 

参数传递时,一定要保证sql语句中的#{minPrice}和map中的 key 值 一样,大小写敏感。否则会报参数错误。 Map 类型作为传入参数使用的并不多,主要有两个原因

  1. Map 是一个键值对的集合,使用者只有通过阅读 键 的值才能明白其作用
  2. Map 不能限定其传入的数据时什么类型,容易造成数据混乱很难管理

正是因为这个原因,所以最好是用Java实体(POJO)作为传参。

Pojo 传入示例

  • 定义包装类型pojo
class productVo {
    Product pro;

    public Product getProduct() {
        return pro;
    }
    public void setProduct(Product product) {
        this.pro = product;
    }
}
  • mapper.xml

在UserMapper.xml中定义用户信息综合查询(查询条件复杂,通过高级查询进行复杂关联查询)。

<select id="searchByPrice" parameterType="productVo" resultType="Product">
    select * from Product where price >= #{pro.minPrice} and price <= #{pro.maxPrice}
select>

(7)-输出映射

本文主要讲解mybatis的输出映射。

输出映射有两种方式

  • resultType
  • resultMap

MyBatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap,**resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用,但是resultType跟resultMap不能同时存在。**在MyBatis进行查询映射时,其实查询出来的每一个属性都是放在一个对应的Map里面的,其中键是属性名,值则是其对应的值。

  1. 当提供的返回类型属性是resultType时,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis对自动的给把对应的值赋给resultType所指定对象的属性。
  2. 当提供的返回类型是resultMap时,因为Map不能很好表示领域模型,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。

resultType

  • 使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。对于SQL语句查询出的字段在相应的pojo中必须有和它相同的字段对应,而resultType中的内容就是pojo在本项目中的位置。因此对于单表查询的话用resultType是最合适的。

简单示例:

  • mapper.xml
<typeAlias alias="Blog" type="com.tiantian.mybatis.model.Blog"/>
<select id="selectBlog" parameterType="int" resultType="Blog">
select * from t_blog where id = #{id}
select>

resultMap

mybatis中使用resultMap完成高级输出结果映射。(一对多,多对多)

resultMap使用方法

如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。

1.定义resultMap

2.使用resultMap作为statement的输出映射类型

  • 定义reusltMap

	 <resultMap type="user" id="userResultMap">
	 	
	 	<id column="id_" property="id"/>
	 	
	 	<result column="username_" property="username"/>
	 
	 resultMap>
  • 使用resultMap作为statement的输出映射类型

    <select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">
        SELECT id id_,username username_ FROM USER WHERE id=#{value}
    select>

小结

使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。

如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。

(8)-动态sql

mybatis核心,对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接、组装。主要有以下四种:

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

if判断

  • mapper.xml
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
  <if test="title != null">
    AND title like #{title}
  if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  if>
select>>

有时候在字符串作为判断条件的时候,同时要判断字符串是否为 null 和是否为空

choose, when, otherwise

有时我们不想应用到所有的条件语句,而只想从中择其一项。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是上面的例子,但是这次变为提供了“title”就按“title”查找,提供了“author”就按“author”查找的情形,若两者都没有提供,就返回所有符合条件的 BLOG

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    when>
    <otherwise>
      AND featured = 1
    otherwise>
  choose>
select>

trim, where, set

where 和 set 都是 trim 的一种具体做法。

  • where 标签的作用:如果该标签包含的元素中有返回值,就插入一个 where ;如果 where后面的字符串是以 AND 和 OR 开头的,就将它们剔除。
<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG 
  <where> 
    <if test="state != null">
         state = #{state}
    if> 
    <if test="title != null">
        AND title like #{title}
    if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    if>
  where>
select>
  • set 标签的作用:如果该标签包含的元素中有返回值,就插入一个 set ;如果 set 后面的字符串是以 逗号 结尾的,就将它们剔除。
<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},if>
      <if test="password != null">password=#{password},if>
      <if test="email != null">email=#{email},if>
      <if test="bio != null">bio=#{bio}if>
    set>
  where id=#{id}
update>

foreach标签

动态 SQL 的另外一个常用的操作需求是对一个集合进行遍历,通常是在构建 IN 条件语句的时候。向sql传递数组或List,mybatis使用foreach解析

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  foreach>
select>

(9)-级联

一对一




<mapper namespace="com.dao.classMapper">

    
    <select id="getClass" parameterType="int" resultMap="ClassResultMap">
        select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
    select>
    
    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" javaType="me.gacl.domain.Teacher">
            <id property="id" column="t_id"/>
            <result property="name" column="t_name"/>
        association>
    resultMap>
    
    
     <select id="getClass2" parameterType="int" resultMap="ClassResultMap2">
        select * from class where c_id=#{id}
     select>
     
     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap2">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" select="getTeacher"/>
     resultMap>
     
     <select id="getTeacher" parameterType="int" resultType="me.gacl.domain.Teacher">
        SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
     select>

mapper>

一对多

mybatis使用resultMap的collection对关联查询的多条记录映射到一个list集合属性中。


    
    <select id="getClass3" parameterType="int" resultMap="ClassResultMap3">
        select * from class c, teacher t,student s where c.teacher_id=t.t_id and c.C_id=s.class_id and  c.c_id=#{id}
    select>
    <resultMap type="me.gacl.domain.Classes" id="ClassResultMap3">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher">
            <id property="id" column="t_id"/>
            <result property="name" column="t_name"/>
        association>
        
        <collection property="students" ofType="me.gacl.domain.Student">
            <id property="id" column="s_id"/>
            <result property="name" column="s_name"/>
        collection>
    resultMap>
    
    
     <select id="getClass4" parameterType="int" resultMap="ClassResultMap4">
        select * from class where c_id=#{id}
     select>
     <resultMap type="me.gacl.domain.Classes" id="ClassResultMap4">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
        <association property="teacher" column="teacher_id" javaType="me.gacl.domain.Teacher" select="getTeacher2">association>
        <collection property="students" ofType="me.gacl.domain.Student" column="c_id" select="getStudent">collection>
     resultMap>
     
     <select id="getTeacher2" parameterType="int" resultType="me.gacl.domain.Teacher">
        SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
     select>
     
     <select id="getStudent" parameterType="int" resultType="me.gacl.domain.Student">
        SELECT s_id id, s_name name FROM student WHERE class_id=#{id}
     select>

另外,下面这篇文章对一对多的resultMap机制解释的很清楚:

MyBatis:一对多表关系详解(从案例中解析)

多对多

总结

将查询用户购买的商品信息明细清单,(用户名、用户地址、购买商品名称、购买商品时间、购买商品数量)

针对上边的需求就使用resultType将查询到的记录映射到一个扩展的pojo中,很简单实现明细清单的功能。


<resultMap type="com.iot.mybatis.po.User" id="UserAndItemsResultMap">
    
    <id column="user_id" property="id"/>
    <result column="username" property="username"/>
    <result column="sex" property="sex"/>
    <result column="address" property="address"/>

    
    <collection property="ordersList" ofType="com.iot.mybatis.po.Orders">
        <id column="id" property="id"/>
        <result column="user_id" property="userId"/>
        <result column="number" property="number"/>
        <result column="createtime" property="createtime"/>
        <result column="note" property="note"/>

        
        <collection property="orderdetails" ofType="com.iot.mybatis.po.Orderdetail">
            <id column="orderdetail_id" property="id"/>
            <result column="items_id" property="itemsId"/>
            <result column="items_num" property="itemsNum"/>
            <result column="orders_id" property="ordersId"/>

            
            <association property="items" javaType="com.iot.mybatis.po.Items">
                <id column="items_id" property="id"/>
                <result column="items_name" property="name"/>
                <result column="items_detail" property="detail"/>
                <result column="items_price" property="price"/>
            association>

        collection>

    collection>
resultMap>

resultMap总结

  • resultType
    • 作用:将查询结果按照sql列名pojo属性名一致性映射到pojo中。
    • 场合:常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历list(list中是pojo)即可。
  • resultMap

使用association和collection完成一对一和一对多高级映射(对结果有特殊的映射要求)。

association:

  • 作用:将关联查询信息映射到一个pojo对象中。
  • 场合:为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。

使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap。

collection:

  • 作用:将关联查询信息映射到一个list集合中。
  • 场合:为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。如果使用resultType无法将查询结果映射到list集合中。

(10)-延迟加载

resultMap可以实现高级映射(使用associationcollection实现一对一及一对多映射),associationcollection具备延迟加载功能。

延迟加载:先从单表查询、需要时再从关联表去关联查询,大大提高数据库性能,因为查询单表要比关联查询多张表速度要快。

需求:

如果查询订单并且关联查询用户信息。如果先查询订单信息即可满足要求,当我们需要查询用户信息时再查询用户信息。把对用户信息的按需去查询就是延迟加载。

使用association实现延迟加载

  • mapper.xml

需要定义两个mapper的方法对应的statement。

1.只查询订单信息

SELECT * FROM orders

在查询订单的statement中使用association去延迟加载(执行)下边的satatement(关联查询用户信息)


<select id="findOrdersUserLazyLoading" resultMap="OrdersUserLazyLoadingResultMap">
    SELECT * FROM orders
select>

2.关联查询用户信息

通过上边查询到的订单信息中user_id去关联查询用户信息,使用UserMapper.xml中的findUserById

<select id="findUserById" parameterType="int" resultType="com.iot.mybatis.po.User">
    SELECT * FROM  user  WHERE id=#{value}
select>

上边先去执行findOrdersUserLazyLoading,当需要去查询用户的时候再去执行findUserById,通过resultMap的定义将延迟加载执行配置起来。

  • 延迟加载resultMap

<resultMap type="com.iot.mybatis.po.Orders" id="OrdersUserLazyLoadingResultMap">
    
    <id column="id" property="id"/>
    <result column="user_id" property="userId"/>
    <result column="number" property="number"/>
    <result column="createtime" property="createtime"/>
    <result column="note" property="note"/>
    
    <association property="user"  javaType="com.iot.mybatis.po.User"
                 select="com.iot.mybatis.mapper.UserMapper.findUserById"
                 column="user_id">
     

    association>

resultMap>

与非延迟加载的主要区别就在association标签属性多了selectcolumn

<association property="user"  javaType="com.iot.mybatis.po.User"
             select="com.iot.mybatis.mapper.UserMapper.findUserById"
             column="user_id">
  • mapper.java
//查询订单关联查询用户,用户信息是延迟加载
public List<Orders> findOrdersUserLazyLoading()throws Exception;
  • 延迟加载配置

mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。

在mybatis核心配置文件中配置:lazyLoadingEnabled、aggressiveLazyLoading

设置项 描述 允许值 默认值
lazyLoadingEnabled 全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载 true/false false
aggressiveLazyLoading 当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。 true/false true

在SqlMapConfig.xml中配置:

<settings>
    
    <setting name="lazyLoadingEnabled" value="true"/>
    
    <setting name="aggressiveLazyLoading" value="false"/>
    
   
settings>

延迟加载思考

不使用mybatis提供的association及collection中的延迟加载功能,如何实现延迟加载??

实现方法如下:

定义两个mapper方法:

  • 查询订单列表
  • 根据用户id查询用户信息

实现思路:

先去查询第一个mapper方法,获取订单信息列表;在程序中(service),按需去调用第二个mapper方法去查询用户信息。

总之,使用延迟加载方法,先去查询简单的sql(最好单表,也可以关联查询),再去按需要加载关联查询的其它信息。

(11)-Mybatis 缓存机制分析-一级缓存与二级缓存

这部分一定要看这一篇文章: 聊聊MyBatis缓存机制
写的非常好。

(12)-spring和mybatis整合

本文主要将如何将spring和mybatis整合,只是作简单的示例,没有使用Maven构建。并展示mybatis与spring整合后如何进行原始dao开发和mapper代理开发。

整合思路

  • 需要spring通过单例方式管理SqlSessionFactory
  • spring和mybatis整合生成代理对象,使用SqlSessionFactory创建SqlSession。(spring和mybatis整合自动完成)
  • 持久层的mapper都需要由spring进行管理。

整合环境

创建一个新的java工程(接近实际开发的工程结构)

jar包:

  • mybatis3.2.7的jar包
  • spring3.2.0的jar包
  • mybatis和spring的整合包:早期ibatis和spring整合是由spring官方提供,mybatis和spring整合由mybatis提供。

sqlSessionFactory

在applicationContext.xml配置sqlSessionFactory和数据源

sqlSessionFactory在mybatis和spring的整合包下。

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-3.2.xsd ">


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

    
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
          destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="10" />
        <property name="maxIdle" value="5" />
    bean>

    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        
        <property name="configLocation" value="mybatis/SqlMapConfig.xml" />
        
        <property name="dataSource" ref="dataSource" />
    bean>
beans>

原始dao开发(和spring整合后)

  • User.xml



<mapper namespace="test">
    
    
    
    <select id="findUserById" parameterType="int" resultType="com.iot.ssm.po.User">
        SELECT * FROM  user  WHERE id=#{value}
    select>


mapper>

在SqlMapconfig.xml中加载User.xml

 
<mappers>
    <mapper resource="sqlmap/User.xml"/>
mappers>  
  • dao(实现类继承SqlSessionDaoSupport)
public interface UserDao {
    //根据id查询用户信息
    public User findUserById(int id) throws Exception;
}

dao接口实现类需要注入SqlSessoinFactory,通过spring进行注入。这里spring声明配置方式,配置dao的bean

让UserDaoImpl实现类继承SqlSessionDaoSupport

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao{


    @Override
    public User findUserById(int id) throws Exception {
        //继承SqlSessionDaoSupport,通过this.getSqlSession()得到sqlSessoin
        SqlSession sqlSession = this.getSqlSession();
        User user = sqlSession.selectOne("test.findUserById",id);

        return user;
    }

}
  • 配置dao

在applicationContext.xml中配置dao


<bean id="userDao" class="com.iot.ssm.dao.UserDaoImpl">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>

mapper代理开发

  • mapper.java
public interface UserMapper {
    //根据id查询用户信息
    User findUserById(int id) throws Exception;

}
  • mapper.xml



<mapper namespace="com.iot.ssm.mapper.UserMapper">

    

    <select id="findUserById" parameterType="int" resultType="user">
        SELECT * FROM  user  WHERE id=#{value}
    select>


mapper>
  • 通过MapperFactoryBean创建代理对象
 

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        //mapperInterface指定mapper接口
        <property name="mapperInterface" value="com.iot.ssm.mapper.UserMapper"/>
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
bean>

此方法问题:需要针对每个mapper进行配置,麻烦。

  • 通过MapperScannerConfigurer进行整个 mapper 包进行扫描(建议使用)

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    
    <property name="basePackage" value="com.iot.ssm.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

bean>

(13)-mybatis逆向工程

mybaits需要程序员自己编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java,mapper.xml、po…)

企业实际开发中,常用的逆向工程方式:由数据库的表生成java代码。

先附上官网链接:

  • MyBatis Generator
  • 逆向工程配置文件


<generatorConfiguration>
    
    <classPathEntry location="F:/cache/mysql-connector-java-5.1.28-bin.jar" />


    <context id="DB2Tables"    targetRuntime="MyBatis3">

        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <property name="suppressAllComments" value="true"/>
        commentGenerator>
        
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost/test" userId="root" password="">
        jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        javaTypeResolver>
        
        <javaModelGenerator targetPackage="com.leige.domain" targetProject="src">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        javaModelGenerator>
        
        <sqlMapGenerator targetPackage="com.leige.domain" targetProject="src">
            <property name="enableSubPackages" value="true"/>
        sqlMapGenerator>
        
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.leige.dao" targetProject="src">
            <property name="enableSubPackages" value="true"/>
        javaClientGenerator>
        
        <table tableName="student" 
            domainObjectName="Student" 
          >table>
           <table tableName="teacher" 
            domainObjectName="Teacher" 
          >table>
    context>
generatorConfiguration>
  • Java 程序
package com.leige.test;

import java.awt.geom.GeneralPath;
import java.awt.im.InputContext;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.log4j.chainsaw.Main;
import org.apache.log4j.lf5.util.Resource;
import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.internal.DefaultShellCallback;

public class MybatisGen {
    public static void generator() throws Exception{
           List<String> warnings = new ArrayList<String>();
           boolean overwrite = true;
           //项目根路径不要有中文,我的有中文,所以使用绝对路径
           File configFile = new File("F:/cache/generatorConfig.xml");
           ConfigurationParser cp = new ConfigurationParser(warnings);
           Configuration config = cp.parseConfiguration(configFile);
           DefaultShellCallback callback = new DefaultShellCallback(overwrite);
           MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
           myBatisGenerator.generate(null);
    }
    public static void main(String[] args) {
        try {
            generator();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

你可能感兴趣的