MyBaties框架入门学习

这篇博客全面介绍了MyBatis框架的使用,包括环境搭建、核心组件、语句执行、事务控制、缓存操作、动态SQL以及注解开发。内容涵盖了映射器XML配置、SQL Mapper接口、结果映射、缓存机制、动态SQL标签的使用,以及级联操作和延迟加载的实现。此外,还讲解了MyBatis逆向工程和分页插件的使用,以提高开发效率。

MyBaties框架学习

MyBatis是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射
框架的好处:
1、规范代码	
2、提高开发效率
搭建MyBatis环境
下载: https://github.com/mybatis/mybatis-3/releases,去https://mvnrepository.com可以查询哪个版本最常被人使用,下载该版本即可(此处选择使用mybatis-3.5.1)。
可以选择登录http://www.mybatis.org/mybatis-3/zh/getting-started.html去查看中文说明文档。

搭建MyBatis环境步骤:

1、创建WEB程序;

2、将jar包复制到WEB-INFO下lib目录(mybatis核心jar包、数据库驱动jar包、日志jar包);

3、在src目录中创建mybatis核心配置文件(文件名自定义,但一般为mybatis-config.xml);

4.、在src目录中创建持久化类(POJO);

5.、在src目录中创建操作持久化类(POJO)的接口以及其对应的映射器(xml):

   接口命名规则:POJO类名+Mapper.java;

   映射器命名规则:POJO类名+Mapper.xml

入门案例

搭建MyBatis环境并测试项目运行(对数据库表进行增删改查)。

步骤1:在mysql中创建taotao数据库,并创建t_student表,表中字段:sid、sname、ssex、sage、saddress、sbirthday、cid,准备数据;

步骤2:在idea中创建web项目,在WEB-INF下创建lib目录,复制MyBatis核心包和log4j核心包、mysql驱动包到lib目录中;
配置核心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="数据库的properties文件名"></properties>
  <!-- 环境 -->
  <environments default="development">
    <environment id="development">
    	<!-- transactionManager:事务mybatis提供了最基本的事务:JDBC-->
      <transactionManager type="JDBC"/>
      <!-- 连接池 采用了默认的池子:pooled,该池子时mybatis提供-->
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <!--加载映射文件-->
  <mappers>
  	 	<!-- 常见第一种引入映射器XML,通过文件路径引入
		<mapper resource="com/wsjy/mapper/StudentMapper.xml"/> -->
		<!-- 常见第二种引入映射器接口,通过包名引入,将对应包下的所有映射器接口都引入 -->
		<package name="com.wsjy.mapper"/>
		<!-- 常见第三种引入某个具体的映射器接口,通过类注册引入,使用类的全限定名
		<mapper class="com.wsjy.mapper.StudentMapper"/> -->
  </mappers>
</configuration>

<configuration>中的子元素有规定的顺序,必须按照对应顺序进行配置,否则报错。以下是核心配置文件的子元素配置顺序:

<?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>配置顺序:可故意配错,从报错信息查看 -->
<configuration><!-- 配置 -->
	<properties/><!-- 属性 -->
	<settings/><!-- 设置 -->
	<typeAliases/><!-- 类型命名 -->
 	<typeHandlers/><!-- 类型处理器 -->
	<objectFactory/><!-- 对象工厂 -->
	<plugins/><!-- 插件 -->
	<environments><!-- 环境配置 -->
		<environment><!-- 环境变量 -->
			<transactionManager/><!-- 事务管理器 -->
			<dataSource/><!-- 数据源 -->
		</environment>
	</environments>
	<mappers/><!-- 映射器 -->
</configuration>

创建持久化类:src/com/wsjy/pojo/Student.java

public class Student {
    private Integer sid;
    private String sname;
    private Integer ssex;
    private Integer sage;
    private String saddress;
    private Date sbirthday;
    private Integer cid;
	
    /*setter/getter/toString*/
}

创建映射器接口:src/com/wsjy/mapper/StudentMapper.java

//映射器接口,接口中的方法名对应映射器<mapper>标签中对应方法的id值
public interface StudentMapper {
    /**
     * 查询全部学生
     * @return
     */
    List<Student> findAll();

    /**
     * 根据sid查询学生
     * @param sid
     * @return
     */
    Student findById(int sid);

    /**
     * 新增学生
     * @param student
     */
    void addStudent(Student student);

    /**
     * 根据sid修改学生
     * @param sid
     */
    void updateStudent(int sid);

    /**
     * 根据sid删除学生
     * @param sid
     */
    void deleteStudent(int sid);
}

创建映射器:src/com/wsjy/mapper/StudentMapper.xml

<?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属性(命名空间),用于区分不同的mapper,全局唯一
	namespace属性的取值为与该映射文件对应接口的全限定类名(包名+类名)
 -->
<mapper namespace="com.wsjy.mapper.StudentMapper">
	<!-- 对应于SQL语句,mapper中有4个子标签,分别是select、insert、update和delete,
		它们分别对应查询、新增、修改、删除操作。
		增删改查子标签上都会有id属性,其值对应于映射器接口中的方法名称
 	-->
    <!-- 查询全部学生 -->
    <select id="findAll" resultType="com.wsjy.pojo.Student">
        select * from t_student
    </select>
	<!-- 根据uid查询 -->
    <select id="findById" resultType="com.wsjy.pojo.Student" parameterType="int">
        select * from t_student where sid=#{sid}
    </select>
	<!-- 新增学生 -->
    <insert id="addStudent" parameterType="com.wsjy.pojo.Student">
        insert into t_student(sname,ssex,sage,saddress,sbirthday) 
        values(#{sname},#{ssex},#{sage},#{saddress},#{sbirthday})
    </insert>
	<!-- 根据uid修改学生 -->
    <update id="updateStudent" parameterType="int">
        update t_student set sname='赵敏' where sid=#{sid}
    </update>
	<!-- 根据uid删除学生 -->
    <delete id="deleteStudent" parameterType="int">
        delete from t_student where sid=#{sid}
    </delete>
</mapper>

创建测试类:src/com/wsjy/test/Test.java

//测试类
public class Test {
    public static void main(String[] args) throws IOException {
        //testFindAll();
        
        //testFindById(5);
        
        //Student stu=new Student();
        //stu.setUname("张三丰");
        //stu.setSsex(1);
        //stu.setSage(100);
        //stu.setSaddress("武当山");
        //stu.setSbirthday("1863-4-18");
        //stu.setCid(4);
        //testAddStudent(stu);
        
        //testUpdateStudent(5);
        
        //testDeleteStudent(10);
    }

    /**
     * 测试根据sid删除学生
     * @param sid
     * @throws IOException
     */
    public static void testDeleteStudent(int sid) throws IOException {
        //1、读取配置文件得到输入流
        InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
        //2、创建SqlSessionFactory的构建器
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3、根据输入流构建SqlSessionFactory
        SqlSessionFactory factory = builder.build(is);
        //4、使用工厂创建SqlSession
        SqlSession sqlSession = factory.openSession(true);
        //5、使用SqlSession对应方法执行映射器中的SQL
        int count = sqlSession.update("com.wsjy.mapper.StudentMapper.deleteStudent",sid);
        System.out.println(count);
        //6、释放资源
        sqlSession.close();
        is.close();
    }

    /**
     * 测试根据sid修改学生
     * @param 
     * @throws IOException
     */
    public static void testUpdateStudent(int sid) throws IOException {
        //1、读取配置文件得到输入流
        InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
        //2、创建SqlSessionFactory的构建器
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3、根据输入流构建SqlSessionFactory
        SqlSessionFactory factory = builder.build(is);
        //4、使用工厂创建SqlSession
        SqlSession sqlSession = factory.openSession(true);
        //5、使用SqlSession对应方法执行映射器中的SQL
        int count = sqlSession.update("com.wsjy.mapper.StudentMapper.updateStudent",sid);
        System.out.println(count);
        //6、释放资源
        sqlSession.close();
        is.close();
    }

    /**
     * 测试新增用户
     * @param student
     * @throws IOException
     */
    public static void testAddStudent(Student student) throws IOException {
        //1、读取配置文件得到输入流
        InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
        //2、创建SqlSessionFactory的构建器
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3、根据输入流构建SqlSessionFactory
        SqlSessionFactory factory = builder.build(is);
        //4、使用工厂创建SqlSession
        SqlSession sqlSession = factory.openSession(true);
        //5、使用SqlSession对应方法执行映射器中的SQL
        int count = sqlSession.insert("com.wsjy.mapper.StudentMapper.addStudent", student);
        System.out.println(count);
        //6、释放资源
        sqlSession.close();
        is.close();
    }

    /**
     * 测试根据sid查询学生
     * @param sid
     * @throws IOException
     */
    public static void testFindById(int sid) throws IOException {
        //1、读取配置文件得到输入流
        InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
        //2、创建SqlSessionFactory的构建器
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3、根据输入流构建SqlSessionFactory
        SqlSessionFactory factory = builder.build(is);
        //4、使用工厂创建SqlSession
        SqlSession sqlSession = factory.openSession();
        //5、使用SqlSession对应方法执行映射器中的SQL
        Student student = sqlSession.selectOne("com.wsjy.mapper.StudentMapper.findById");
        System.out.println(student);
        //6、释放资源
        sqlSession.close();
        is.close();
    }

    /**
     * 测试查询全部学生
     * @throws IOException
     */
    public static void testFindAll() throws IOException {
        //1、读取配置文件得到输入流
        InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
        //2、创建SqlSessionFactory的构建器
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //3、根据输入流构建SqlSessionFactory
        SqlSessionFactory factory = builder.build(is);
        //4、使用工厂创建SqlSession
        SqlSession sqlSession = factory.openSession();
        //5、使用SqlSession对应方法执行映射器中的SQL
        List<Student> stus = sqlSession.selectList("com.wsjy.mapper.StudentMapper.findAll");
        //6、遍历执行结果
        for(Student stu:stus){
            System.out.println(stu);
        }
        //7、释放资源
        sqlSession.close();
        is.close();
    }

}

以上案例也可以选择不使用映射器,而选择使用注解来实现。使用注解时,无需创建映射器src/com/wsjy/mapper/StudentMapper.xml,只需要在src/com/wsjy/mapper/StudentMapper.java中添加注解即可实现相同功能。

//映射器接口,接口中的方法名对应映射器<mapper>标签中对应方法的id值
public interface StudentMapper {
	@select("select * from t_student")
    List<Student> findAll();
}

当注解和XML同时存在,程序运行出错,因为MyBatis不知道使用哪种方式进行处理。

由于实际开发过程中SQL语句非常复杂,使用注解时会造成代码可读性变差。而且在使用动态SQL时,需要根据一定逻辑来决定SQL语句的执行,使用注解就变得难以实现了。另外,如果使用XML来实现映射器,可以相互引用,而注解则不能做到这一点。

因此,不建议使用注解来代替XML。

MyBatis核心组件
MyBatis的核心组件分为4个部分:
  • SqlSessionFactoryBuilder(构造器):
根据配置或者代码来生成SqlSessionFactory,采用的是分步构建的Builder模式;
  • SqlSessionFactory(工厂接口):
依靠它来生成SqlSession,使用的是工厂模式;
  • SqlSession(会话):
一个可以发送SQL执行返回结果,也可以获取Mapper的接口。
  • SQL Mapper(映射器):
MyBatis中的组件,由一个java接口和XML文件(也可以是注解)构成,需要给出对应的SQL和映射规则。它负责发送SQL去执行,并返回结果;
语句执行方法
语句执行方法被用来执行定义在 SQL 映射的 XML 文件中的 SELECT、INSERT、UPDAET和 DELETE 语句。它们都会自行解释,每一句都使用语句的 ID 属性和参数对象,参数可以是原生类型(自动装箱或包装类) 、JavaBean、POJO 或 Map。
  • Object selectOne(String statement, Object parameter)
  • List selectList(String statement, Object parameter)
  • int insert(String statement, Object parameter)
  • int update(String statement, Object parameter)
  • int delete(String statement, Object parameter)

selectOne和selectList的不同仅仅是selectOne必须返回一个对象。如果多余一个,或者没有返回(或返回了null),那么就会抛出异常。如果不确定返回多少对象,使用selectList。如果你想检查一个对象是否存在,那么最好返回统计数(0或1)。

事务控制方法
控制事务有四个方法。如果已经选择自动提交或正在使用外部事务管理器,这此方法就没有任何效果了。如果你正在使用JDBC事务管理,由Connection实例来控制,那么这四个方法就会派上用场: 
  • void commit()
  • void commit(boolean force)
  • void rollback()
  • void rollback(boolean force)
    在创建SqlSession实例时可以指定是否自动提交:
//1、读取配置文件得到输入流
InputStream is= Resources.getResourceAsStream("mybatis-config.xml");
//2、创建SqlSessionFactory的构建器
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3、根据输入流构建SqlSessionFactory
SqlSessionFactory factory = builder.build(is);
//4、使用工厂创建SqlSession
/*SqlSessionFactory的openSession()方法可以传boolean类型的参数。
true代表事务自动提交,false或省略代表事务不会自动提交。*/
SqlSession sqlSession = factory.openSession();
清理Session级的缓存
SqlSession实例有一个本地缓存在执行update,commit,rollback和close时被清理。要明确地清除缓存,可以调用clearCache()。 
关闭SqlSession
必须保证创建的SqlSession在使用完成后被关闭,因为一个SqlSession就相当于从数据库连接池中取到的一个连接,如果只开不关,数据库连接池中的连接将很快被消耗完毕,从而导致系统瘫痪。
调用close() 可以关闭SqlSession。
获取映射器

使用MyBatis提供的SQL Mapper接口编程技术,能提高代码的可读性和可维护性,获取映射器的方法如下:

<T> T getMapper(Class\<T> type)  
实质上是使用了动态代理技术,使用getMapper()获取了参数(接口)的实例,从而可以使用该实例来调用对应接口的方法。getMapper()的参数为被代理的接口Class对象(接口.class即可获取)。

注意:在实际应用开发过程中,一般会使用映射器来代替SqlSession的使用。

核心组件生命周期

SqlSessionFactoryBuilder
查看源码可知,SqlSessionFactoryBuilder的作用就是创建SqlSessionFactory,当SqlSessionFactory创建成功后,SqlSessionFactoryBuilder就失去了作用,因此,它只能存在于创建SqlSessionFactory的方法中,不能长期存在。
SqlSessionFactory
SqlSessionFactory相当于是一个数据库连接池,其作用是创建SqlSession接口对象(相当于连接池中的连接)。SqlSessionFactory对象一旦被创建,在整个MyBatis应用运行期间就不应被销毁。因此,其生命周期与MyBatis应用生命周期是一样的。
对于一个MyBatis应用来说,如果有多个数据库连接池,不利于对数据库资源的控制,也会导致数据库连接资源被快速消耗,出现系统宕机等情况。因此,SqlSessionFactory应该是以单例的形式出现,在整个MyBatis应用中被共享。
SqlSession
SqlSession相当于数据库连接池中的一个连接,因此,在使用SqlSession处理完某个业务请求后,应将其关闭并交还给SqlSessionFactory,避免数据库连接池中的资源被快速消耗,从而导致系统瘫痪。
SQL Mapper
Mapper是一个接口,由SqlSession创建,因此其生命周期应小于等于SqlSession。

入门案例优化

使用属性文件封装数据库连接信息
在入门案例中,数据库连接信息直接配置在核心配置文件\<environments>中,这样的话,一旦要改变数据库连接,就需要修改核心配置文件,这种方式并不可取。
一般会将数据库连接信息存放到一个专门的properties属性文件中,当要进行修改时,直接修改该属性文件即可。

创建src/jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/taotao
username=root
password=root

核心配置文件进行以下配置:

<?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="jdbc.properties"/>
    
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <!-- 使用${}直接引用外部属性文件中的属性名 -->
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/wsjy/mapper/StudentMapper.xml"/>
    </mappers>
</configuration>
使用映射器替换SqlSession

在入门案例中的测试类中,有以下代码:

public class Test {
    private static Logger logger = Logger.getLogger(Test.class);
    public static void main(String[] args) {
        ......
        //4、使用SqlSession对象调用对应方法执行映射器中的SQL并返回结果
        List<Student> stus = sqlSession.selectList("com.wsjy.mybatis.mapper.StudentMapper.findAll");
       ......
    }
}
此处使用SqlSession对象调用selectList()进行查询,该方法传递的参数必须是对应接口的完全限定名+方法名,参数传递冗长,并且极容易出现遗漏,从而导致程序运行出错。
一般在实际开发中,会使用映射器来解决这个问题。
public class Test {
    private static Logger logger = Logger.getLogger(Test.class);
    public static void main(String[] args) {
        ......
        //4、使用SqlSession对象调用对应方法获取映射器
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        //5、使用接口代码对象来调用接口方法,得到返回结果
        List<Student> stus = mapper.findAll();
       ......
    }
}
使用别名

在入门案例中的映射器XML文件中有以下代码:

<select id="findAll" resultType="com.wsjy.pojo.Student">
	select * from t_student
</select>

​ 以上代码中,<select>标签的resultType属性取值为pojo类的完全限定名,过于冗长的属性值容易造成阅读困难,可以使用别名来解决此问题。
设置别名必须在核心配置文件mybatis-config.xml文件中配置,通过使用<typeAliases>标签进行设置。

<!-- 自定义别名的第一种方式,使用typeAlias标签的alias属性指定别名,别名一般为对应的类名,将首字母小写即可,使用type属性指定该别名对应的全限定名称 -->
	<typeAliases>
		<typeAlias alias="student" type="com.wsjy.pojo.Student"/>
	</typeAliases>
	<!-- 自定义别名的第二种方式,使用package标签的name属性进行包扫描,此时mybatis将扫描整个包,然后将包下所有类的类名首字母小写后作为该类的别名使用 -->
	<typeAliases>
		<package name="com.wsjy.pojo"/>
	</typeAliases>

使用包扫描的方式应注意,包扫描范围不宜过大,否则可能会造成重名。
假设有两个类:
com.wsjy.pojo.user.Student
com.wsjy.pojo.employee.Student

此时如果使用\<package name="com.wsjy.pojo"/>进行包扫描,由于pojo的两个子包中都有Student类,会出现重名的Student。

出现重名时,可以使用注解来解决重名问题
在com.wsjy.pojo.user.Student中增加注解:

@Alias("user")
public class Student{......}

在com.wsjy.pojo.employee.Student中增加注解:

@Alias("employee")
public class Student{......}

注册别名后,入门案例的映射器XML文件中的<select>标签可以直接使用别名填充resultType属性:

<select id="findAll" resultType="Student">
	select * from t_Student
</select>
使用log4j记录日志
MyBatis默认使用其自带的日志进行程序运行中的信息记录,如在程序中需要使用日志记录自己的信息,则需要单独导入对应的日志,比如log4j。
创建log4j配置文件:src/log4j.properties或src/log4j.xml(二者任选其一,此处选择使用properties)。
设置Logger输出级别和输出目的地
log4j.rootLogger=error, stdout,logfile
指定log4j日志记录范围
log4j.logger.com.wsjy=debug
把日志信息输出到控制台
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %p %m%n
把日志信息输出到文件:wsjy.log
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=wsjy.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %p %m%n

在核心配置文件mybatis-config.xml中启用log4j。

<settings>
    <!-- 对应的值可到MyBatis说明文档中settings中复制 -->
    <setting name="logImpl" value="LOG4J"/>
</settings>

在入门安全的测试类中,获取log4j日志对象,进行日志记录。

public class Test {
    private static Logger logger = Logger.getLogger(Test.class)
    public static void main(String[] args) {
        ......
        //6、遍历执行结果
        for(Student stu:stus){
            logger.debug(stu);
        }
        ......
    }
}

如果只是在控制台打印执行的sql语句可以在核心配置文件中设置
打印sql语句设置

<setting name="logImpl" value="STDOUT_LOGGING"/>
封装工具类
在入门案例的测试类中,获取和关闭SqlSession的代码大量冗余,可将冗余代码封装成工具类,需要使用时调用工具类方法即可。

创建工具类src/com/wsjy/util/MyBatisUtil.java

public class MyBatisUtil {
    private static Logger logger = Logger.getLogger(MyBatisUtil.class);
    /*
     * 将SqlSessionFactory声明为静态成员变量,而真正创建SqlSessionFactory对象的过程放到静态代码块中
     * 在类加载时最先执行的就是静态代码块,因此确保SqlSessionFactory对象不为空且唯一
     * */
    private static SqlSessionFactory sessionFactory;
    static{
        InputStream is=null;
        try {
            //通过Resources类的getResourceAsStream()方法读取核心配置文件得到文件输入流
            is= Resources.getResourceAsStream("mybatis-config.xml");
            //创建工厂建造者对象并使用其bulid()方法根据文件输入流得到工厂对象
            sessionFactory=new SqlSessionFactoryBuilder().build(is);
        } catch (IOException e) {
            logger.debug(e);
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /*
     * 通过createSqlSession()获取SqlSession对象
     * */
    public static SqlSession createSqlSession(){
        //openSession()参数为true时,代表自动事务提交,无参或参数为false时代表关闭事务自动提交
        return sessionFactory.openSession(true);
    }
    /*
     * 关闭sqlSession对象
     * */
    public static void closeSqlSession(SqlSession session){
        if (session!=null) {
            session.close();
        }
    }
}

入门案例中的测试类中使用工具获取SqlSession实例:

public class TestMyBatis {
    private static Logger logger = Logger.getLogger(TestMyBatis.class);

    public static void main(String[] args) throws IOException {
        testFindAll();
    }

    public static void testFindAll() throws IOException {
        //1、使用工具类创建SqlSession
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        //2、创建接口代理类
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        //3、使用代理类执行方法
        List<Student> stus = mapper.findAll();
        //4、遍历执行结果
        for(Student stu:stus){
            logger.debug(stu);
        }
        MyBatisUtil.closeSqlSession(sqlSession);
    }
    //其他方法也可使用工具类进行改造
}
使用XML开发MyBatis应用

MyBatis的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的XML文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis致力于减少使用成本,让用户能更专注于SQL代码。

​ SQL映射文件(****Mapper.xml)只有很少的几个顶级元素(无顺序之分):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射新增语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。
CURD操作
select
查询语句是 MyBatis 中最常用的元素之一,只能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。
MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。
select 元素允许你配置很多属性来配置每条SQL语句的行为细节。
<select
  id="selectPerson"
  parameterType="int"
  parameterMap="deprecated"
  resultType="hashmap"
  resultMap="personResultMap"
  flushCache="false"
  useCache="true"
  timeout="10"
  fetchSize="256"
  statementType="PREPARED"
  resultSetType="FORWARD_ONLY">
    查询语句
</select>
select元素属性详解
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
resultType期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false。
resultSets这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。
insert,update和delete

数据变更语句 insert,update 和 delete 的实现非常接近:

<insert
  id="insertAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  keyProperty=""
  keyColumn=""
  useGeneratedKeys=""
  timeout="20">
    新增语句
</insert>

<update
  id="updateAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
    更新语句
</update>

<delete
  id="deleteAuthor"
  parameterType="domain.blog.Author"
  flushCache="true"
  statementType="PREPARED"
  timeout="20">
    删除语句
</delete>
insert, update 和 delete元素属性详解
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
主键回填
	在实际开发过程中,有些需求中会用某条被新增到数据库表中记录的主键。可以使用useGeneratedKeys属性和keyProperty配合,使用主键回填来将数据库中自增长产生的主键值填充到对象中,而不必再次执行查询操作以获取该对象新增后生成的主键值。
修改StudentMapper.xml
<!-- 使用useGeneratedKeys和keyProperty配合实现主键回填 -->
<insert id="addStudent" parameterType="student" 
        useGeneratedKeys="true" keyProperty="sid">
    insert into t_student(sname,ssex,sage,saddress,sbirthday) 
    values(#{sname},#{ssex},#{sage},#{saddress},#{sbirthday})
</insert>
测试类:
public static void main(String[] args) throws IOException {
        Student stu=new Student();
        stu.setSname("张无忌");
        stu.setSsex("男");
        stu.setSage(25);
        stu.setSaddress("光明顶");
        stu.setSbirthday(Date.valueOf("1853-8-8"));
    	//第一次输出,此处的sid值一定为null
        System.out.println(stu.getSid());
        addStudent(stu);
    	//第二次输出,由于使用了主键回填,此处可以获取数据库中自动增长设置的主键值
        System.out.println(stu.getSid());
    }
     public static void addStudent(Student student){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        mapper.addStudent(student);
        MyBatisUtil.closeSqlSession(sqlSession);
    }
parameterType
在JDBC中,对SQL语句的传参是通过PreparedStatement中对象进行,完全由java代码实现,因此不存在SQL语句和java代码参数的数据类型转换的问题。
而使用MyBatis框架时,向SQL语句传参是在XML中实现,向接口方法传参则是在java中实现的。此时如何能确定在java中向接口方法传递的参数就一定与XML中传递给SQL语句的参数类型是一致的呢?
MyBatis通过typeHandler来解决以上问题。
在typeHandler中,分为jdbcType和javaType。jdbcType用于定义数据库中的数据类型,javaType用于定义java类型,而typeHandler的作用就是承担jdbcType和javaType之间的相互转换。
MyBatis定义了很多typeHandler,足够应付一般场景。因此一般情况下不需要显式配置typeHandler、jdbcType和javaType,因为MyBatis会探测应该使用什么类型的typeHandler进行处理。除了某些特定场景,比如需要使用自定义枚举,又或者是数据库使用了特殊数据类型。
parameterType属性指定jdbcType,可以传递的参数类型有:java基本数据类型(自动装箱)及其包装类、javaBean以及Map。
  • java基本数据类型及其包装类,无需使用类全限定名,直接写类型即可。比如整形可以int,也可使用integer。
  • javaBean,如果使用typeAliases注册了别名,可使用别名,否则要使用类全限定名(包名+类名)。
  • Map,传递多个参数时,可以使用Map集合进行参数传递。
使用Map传递多个参数
在入门案例中已经使用过java基本数据类型和javaBean进行参数传递,在此处测试使用Map传递多个参数。
映射器XML:src/com/wsjy/mapper/StudentMapper.xml
<?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="com.wsjy.mapper.StudentMapper">
    <!-- 使用Map实现多参数传递,SQL语句中引用的是Map的key
 		此处使用占位符形式传参,因此,在调用映射器接口方法传参时,需要在传递的真实值处加%
	-->
    <select id="findBySnameAndSaddress" resultType="student" parameterType="map">
        SELECT * FROM t_student
        WHERE sname LIKE #{sname}
        AND saddress LIKE #{saddress};
    </select>
    <!-- 使用Map实现多参数传递,SQL语句中引用的是Map的key
		此处使用了concat()进行了拼接,则在调用映射器接口方法传参时,无需传递%
    <select id="findBySnameAndSaddress" resultType="student" parameterType="map">
        SELECT * FROM t_student
        WHERE sname LIKE concat('%',#{sname},'%')
        AND saddress LIKE concat('%',#{saddress},'%');
    </select> -->
</mapper>

在测试类中增加对应方法:testFindBySnameAndSaddress(Map<String,Object> params)

public static void main(String[] args) throws IOException {
        Map params=new HashMap();
        params.put("sname","钱");
        params.put("saddress","京");
        testFindBySnameAndSaddress(params);
    }

    public static void testFindBySnameAndSaddress(Map<String,Object> params){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> stus = mapper.findBySnameAndSaddress(params);
        for(Student stu:stus){
            logger.debug(stu);
        }
        MyBatisUtil.closeSqlSession(sqlSession);
    }
#{}与${}
MyBatis的SQL语句可以使用#{}与${}两种方式获取传递的参数。
	#{}:使用#{}获取的参数,会以“?“占位符的形式传入SQL,以预编译PreparedStatement的方式执行SQL;
	${}:使用${}获取的参数,会以字符串拼接的方式传入SQL,以Statement的方式执行SQL;
如果使用${}获取传递的参数,上例中的代码应做如下修改。

映射器XML:

<?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="com.wsjy.mapper.StudentMapper">
    <!-- 使用Map实现多参数传递,SQL语句中引用的是Map的key -->
    <select id="findBySnameAndSaddress" resultType="student" parameterType="map">
        SELECT * FROM t_student
        WHERE sname LIKE '%${sname}%'
        AND saddress LIKE '%${saddress}%';
    </select>
</mapper>

测试类:

public static void main(String[] args) throws IOException {
        Map params=new HashMap();
        params.put("sname","钱");
        params.put("saddress","京");
        testFindBySnameAndSaddress(params);
    }

一般来说,不应使用${}获取参数。
传递多个参数时,可以采用Map集合、注解以及javaBean三种方式传递,Map集合不推荐使用,因为不直观,而参数个数<=5时,推荐使用注解传参,>5时,推荐使用javaBean。

使用注解传递多个参数

在映射器接口studentMapper.java中添加如下方法:

public interface StudentMapper {
	......
    /**
     * 新增学生:根据学生sid修改学生信息
     * @return
     */
    int updateStudent(@Param("sid")Integer sid,@Param("ssex")String ssex,@Param("sage")Integer sage);
}

在映射器XML文件studentMapper.xml进行如下配置:

<?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="com.wsjy.mapper.StudentMapper">
	......
    <!-- 根据sid修改学生性别和年龄:此处使用注解传参,无需指定parameterType属性 -->
    <update id="updateStudent">
        update t_student set ssex=#{ssex},sage=#{sage} where sid=#{sid}
    </update>
</mapper>

测试类:

public static void main(String[] args) throws IOException {
        testUpdateStudent(4,"男",18);
    }
    public static void testUpdateStudent(Integer sid,String ssex,Integer sage){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        int count = mapper.updateStudent(sid,ssex,sage);
        logger.debug(count);
        MyBatisUtil.closeSqlSession(sqlSession);
    }

​ 需要注意的是,注解和javaBean可以混合使用。比如做分页查询(根据姓名和年龄做模糊查询,需要做分页,此时有两个持久化类:Student、Page):

​ 映射器接口:

public List<Student> findByMix(@param("student") Student student,@param("page") Page page);

​ 映射器XML文件:

	<select id="findByMix" resultType="student">
		select * from t_student 
		where Studentname like concat('%',#{student.sname},'%') 
		and age between 25 and 40
		limit #{page.start},#{page.end}
	</select>
resultType
指定SQL语句执行后的返回类型,实质上也是一个jdbcType,只要是select语句,此属性必须设置。
resultType属性的取值只能是java基本类型及其包装类,也可以是javaBean(没有别名时必须为类全限定名)。resultType没有返回集合的概念,就算是SQL语句返回的本身是一个集合,resultType也只能设置为集合中包含的元素类型。
比如入门案例中的查询全部方法findAll(),此时返回的是一个resultSet结果集,但resultType属性值只能设置为Student。
resultMap
resultMap元素用于结果映射,它是 MyBatis 中最重要最强大的元素。使用它可以让你从90% 的 JDBC中的ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。
实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。resultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。 
在使用resultMap时,完全可以不用显式地配置它们。resultType其实就是resultMap的一种隐式实现方式。使用resultType时,MyBatis会在幕后自动创建一个 resultMap,再根据属性名来映射列到 JavaBean 的属性上。
resultMap的应用

数据库字段名与持久化类属性名不一致

	如果数据库中字段为sub_no,而持久化类中字段名为subNo,此时可以选择使用resultMap来解决。

步骤1:在taotao中新增科目表t_subject,表中有字段sub_no、sub_name,添加数据。

步骤2:创建持久化类:src/com/wsjy/pojo/Subject.java

package com.wsjy.pojo;

public class Subject {
    private Integer subNo;
    private String subName;
    
	/*setter/getter/toString*/
}

​ 创建映射器接口:src/com/wsjy/mapper/SubjectMapper.java

public interface SubjectMapper {
    /**
     * 查询全部科目信息
     * @return
     */
    List<Subject> findAll();
}

创建映射器XML:src/com/wsjy/mapper/SubjectMapper.xml

<?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="com.wsjy.mapper.SubjectMapper">
    <!-- 查询全部 -->
    <select id="findAll" resultType="subject">
        SELECT * FROM t_subject
    </select>
</mapper>

​ 测试类:

	public static void main(String[] args) throws IOException {
        testFindAll();
    }

    /**
     * 测试查询全部科目
     */
    public static void testFindAll(){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        SubjectMapper mapper = sqlSession.getMapper(SubjectMapper.class);
        List<Subject> subs = mapper.findAll();
        for (Subject sub : subs) {
            logger.debug(sub);
        }
    }

运行程序后查看结果,subNo和subName值为空,其原因就是持久化类的属性字段名和t_subject数据库表的字段名不匹配,MyBatis自动映射无法生效导致的。

​ 一般出现这种问题有三种解决方案:

  1. 持久化类与数据库表字段名规范书写,让MyBatis自动映射;
  2. 在映射器XML中显式配置resultMap来解决;
  3. 在SQL语句中设置与持久化类字段名相同的别名来解决;

显式配置resultMap

​ 修改映射器XML文件,配置resultMap:

<?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="com.wsjy.mapper.SubjectMapper">
    <!--
		resultMap的id用于唯一标记自己,type表示映射的持久化类
	-->
    <resultMap id="subjectResultMap" type="subject">
        <!--
			id子标签用于映射数据库表的主键字段,
			property取值为持久化类的属性字段名,
			column取值为数据库表的字段名
		-->
        <id property="subNo" column="sub_no"></id>
        <!--
			result子标签用于映射数据库表中非主键字段
		-->
        <result property="subName" column="sub_name"></result>
    </resultMap>
    <!-- 查询全部 -->
    <!--
		要使用显式配置的resultMap,使用resultMap设置返回类型,其值指向<resultMap>标签的id属性值
		注意:resultMap和resultType不能共存,只能任选一个
	-->
    <select id="findAll" resultMap="subjectResultMap">
        SELECT * FROM t_subject
    </select>
</mapper>

​ 此时运行测试程序,发现生成的持久类对象赋值正常。

在SQL语句中设置查询字段别名

​ 也可以在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="com.wsjy.mapper.SubjectMapper">
	<!--查询全部-->
    <!--注意:字段别名必须和持久化类属性字段名一致-->
    <select id="findAll" resultType="subject">
        SELECT
            sub_no as subNo,
            sub_name as subName,
         FROM t_subject
    </select>
</mapper>
自定义枚举typeHandler

​ 入门案例中,t_student表中的性别字段为bit类型,值为0或1。在之前的案例中,持久化类Student中对应属性使用的Integer类型进行存储。

​ 实际应用中,由于不希望性别的值是数字,一般会将性别定义成枚举。但这么做之后,就会导致javaType与jdbcType不匹配,从而无法实现对象的装配,如何解决?

​ 可以使用自定义类型转换器typeHandler来实现javaType与jdbcType的相互转换。

​ 需求:将数据库表t_student表中的ssex字段(BIT)转换为枚举类型。

​ 创建枚举类:src/com/wsjy/pojo/SexEnum.java

public enum SexEnum {
    FEMALE(0,"女"),
    MALE(1,"男");
    private int id;
    private String name;
    
    /*setter/getter*/
    SexEnum(int id,String name) {
        this.id=id;
        this.name=name;
    }
    /* 自定义方法,根据传入的参数获取枚举的相关属性,参数为数据库表中代表性别的字段的值
     * 该方法主要为制作类型转换器使用
     * */
    public static SexEnum getSexById(int id){
        for (SexEnum sex : SexEnum.values()) {
            if (sex.getId()==id) {
                return sex;
            }
        }
        return null;
    }
}

​ 修改持久化类:src/com/wsjy/pojo/Student.java

package com.wsjy.pojo;

import java.io.Serializable;
import java.sql.Date;
import java.util.List;

public class Student {
    private Integer sid;
    private String sname;
    //将性别类型设置为枚举类型
    private SexEnum ssex;
    private Integer sage;
    private String saddress;
    private Date sbirthday;
    private Integer cid;
    
    /*setter/getter/toString*/
}

​ 创建性别枚举类型转换器:src/com/wsjy/pojo/SexEnumTypeHandler.java

//映射的javaType
@MappedTypes(SexEnum.class)
//映射的jdbcType
@MappedJdbcTypes(JdbcType.BIT)
public class SexEnumTypeHandler implements TypeHandler<SexEnum> {
    @Override
    /*
     * 根据列名获取枚举值
     * 在此处的含义为在查询得到的结果集中根据性别的列名获取性别的列值,数据库中一般以bit的形式出现
     * 然后调用枚举类型的getSexById()方法,返回得到的结果
     * */
    public SexEnum getResult(ResultSet rs, String columnName) throws SQLException {
        //从结果集中根据列名获取列值
        int id=rs.getInt(columnName);
        //调用枚举类的getSexById()
        return SexEnum.getSexById(id);
    }

    @Override
    /*
     * 根据列号获取枚举值
     * */
    public SexEnum getResult(ResultSet rs, int columnIndex) throws SQLException {
        int id=rs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }

    @Override
    /*
     * 专用于存储过程,根据列号获取枚举
     * */
    public SexEnum getResult(CallableStatement cs, int columnIndex) throws SQLException {
        int id=cs.getInt(columnIndex);
        return SexEnum.getSexById(id);
    }

    @Override
    /*
     * 将枚举对象的id值进行预编译
     * */
    public void setParameter(PreparedStatement ps, int i, SexEnum sexEnum, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, sexEnum.getId());
    }
}

​ 修改核心配置文件:src/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="jdbc.properties"/>

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <typeAliases>
        <package name="com.wsjy.pojo"/>
    </typeAliases>
    <!--配置类型转换器,实现JavaType与jdbcType的相互转换-->
    <typeHandlers>
        <typeHandler jdbcType="BIT"
                     javaType="com.wsjy.pojo.SexEnum"
                     handler="com.wsjy.pojo.SexEnumTypeHandler"/>
    </typeHandlers>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.wsjy.mapper"/>
    </mappers>
</configuration>

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
    <resultMap id="studentResultMap" type="student">
        <id property="sid" column="sid"></id>
        <result property="sname" column="sname"></result>
        <!--使用typeHandler属性指定类型转换器进行处理-->
        <result property="ssex" column="ssex" typeHandler="sexEnumTypeHandler"></result>
        <result property="sage" column="sage"></result>
        <result property="saddress" column="saddress"></result>
        <result property="sbirthday" column="sbirthday"></result>
        <result property="cid" column="cid"></result>
        <association property="classes" javaType="classes" column="cid"
                     select="com.wsjy.mapper.ClassesMapper.findClassByCid"/>
        <collection property="subs" ofType="subject" column="sid"
                    select="com.wsjy.mapper.SubjectMapper.findSubjectsBySid" />
    </resultMap>

    <select id="findAll" resultType="student">
        <include refid="queryStudent"></include>
    </select>

</mapper>

​ 调用测试类的findAll(),可以看到此时所有查询的学生信息中,性别的值都被装配成自定义的枚举常量。

级联

​ 级联在关联映射中是个重要的概念,指当主加载对象执行操作时,被关联对象是否同步执行同一操作。

​ 要想实现级联,需要先了解表与表之间的映射关系。

  • 一对一:一个人只能有一个身份证,一个身份证也只能属于一个人;
  • 一对多:一个班级可以有多个学生;
  • 多对一:多个学生属于同一个班级;
  • 多对多:一个学生可以有多个老师,一个老师也可以有多个学生;

​ 在MyBatis中,没有多对一和多对多的级联,只有一对一和一对多的级联。另外,MyBatis提供了鉴别器,这是一种根据某些条件决定采用具体实现类级联的方案。比如,学生进行体检,需要根据性别去进行区分。

一对一

​ 需求:查询学生信息时,将其对应的班级信息查询出来。

​ 在taotao中新增班级表t_class,表中有字段cid、cname、openingDate、studentNumber(t_class表的cid属性被t_student作为外键引用),添加数据。

​ 创建班级表对应的持久化类:src/com/wsjy/pojo/Classes.java

public class Classes {
    private Integer cid;
    private String cname;
    private Integer studentNumber;
    private Date openingDate;
    
    /*setter/getter/toString*/
}

​ 数据库表之间的关系可以通过外键来实现,无论是逻辑外键还是物理外键都可,但持久化类之间也需要建立关系:在从表对应的持久化类中添加主表所对应的持久化类引用。

​ 修改持久化类:src/com/wsjy/pojo/Student.java

public class Student {
    private Integer sid;
    private String sname;
    private String ssex;
    private Integer sage;
    private String saddress;
    private Date sbirthday;
    private Integer cid;
	//主表实体引用,实现一对一关系映射
    private Classes classes;

    /*setter/getter/toString*/
}

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
    <!-- 配置resultMap,使student与classes实现关联 -->
    <resultMap id="studentResultMap" type="student">
        <id property="sid" column="sid"></id>
        <result property="sname" column="sname"></result>
        <result property="ssex" column="ssex"></result>
        <result property="sage" column="sage"></result>
        <result property="saddress" column="saddress"></result>
        <result property="sbirthday" column="sbirthday"></result>
        <result property="cid" column="cid"></result>
        <!--
			此处使用级联查询
			一对一,使用<association>
			property指向student对象的classes属性
			javaType属性表示关联实体对象的java类型,
			select表示关联实体通过什么方式获取
			column属性表示关联实体信息使用哪个属性来获取
 		-->
        <association property="classes" javaType="classes" select="com.wsjy.mapper.ClassesMapper.findClassByCid" column="cid"/>
    </resultMap>
    <!-- 根据sid查询学生信息 -->
    <select id="findById" parameterType="int" resultMap="studentResultMap">
        SELECT * FROM t_student WHERE sid=#{sid};
    </select>

</mapper>

​ 如不使用级联,也可以直接在<association>中对属性直接进行装配:

<?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="com.wsjy.mapper.StudentMapper">
<!-- 配置resultMap,使student与classes实现关联 -->
<resultMap id="studentResultMap" type="student">
  <id property="sid" column="sid"></id>
  <result property="sname" column="sname"></result>
  <result property="ssex" column="ssex"></result>
  <result property="sage" column="sage"></result>
  <result property="saddress" column="saddress"></result>
  <result property="sbirthday" column="sbirthday"></result>
  <result property="cid" column="cid"></result>
  <!--
	  不使用级联,可只配property和javaType
-->
  <association property="classes" javaType="classes">
      <id property="cid" column="cid"></id>
  	<result property="cname" column="cname"></result>
  	<result property="studentNumber" column="studentNumber"></result>
      <result property="openingDate" column="openingDate"></result>
  </association>
</resultMap>
<!-- 根据sid查询学生信息,不使用级联,写连接查询 -->
<select id="findById" parameterType="int" resultMap="studentResultMap">
  SELECT * FROM t_student s,t_class c WHERE s.cid=c.cid and s.sid=#{sid}
</select>

</mapper>

​ 一般使用级联,因为直接装配无法实现延迟加载。

​ 测试类:

	public static void main(String[] args) throws IOException {
        testFindStudentBySid(2);
    }

    //根据学生sid查询学生,级联查询班级
    public static void testFindById(int sid){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student stu = mapper.findStudentBySid(sid);
        logger.debug(stu);
        logger.debug(stu.getClasses());
        MyBatisUtil.closeSqlSession(sqlSession);
    }

​ 从测试方法运行的日志记录中可以看到执行了两条SQL语句,返回一条学生信息和一条班级信息,此时级联查询实现成功。

一对多

​ 需求:查询班级信息时,同时将该班所有学生的信息全部查询出来。

​ 创建持久化类对应的映射器接口:src/com/wsjy/mapper/ClassesMapper.java

public interface ClassesMapper {
    /**
     * 根据班级编号查询班级信息
     * @param cid
     * @return
     */
    Classes findClassByCid(int cid);
}

​ 创建持久化类对应的映射器XML:src/com/wsjy/mapper/ClassesMapper.xml

<?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="com.wsjy.mapper.ClassesMapper">
    <resultMap id="classesResultMap" type="classes">
        <id property="cid" column="cid"></id>
        <result property="cname" column="cname"></result>
        <result property="studentNumber" column="studentNumber"></result>
        <result property="openingDate" column="openingDate"></result>
        <!--
			一对多级联:collection
			property指向classes对象的stus属性
			ofType属性返回集合中的java类型,一对多不能使用javaType,否则报返回记录数超限异常
			select表示关联实体通过什么方式获取
			column属性表示关联实体信息使用哪个属性来获取
		-->
        <collection property="stus" ofType="student" select="com.wsjy.mapper.StudentMapper.findStudentsByCid" column="cid">
        </collection>
    </resultMap>
    <select id="findClassByCid" parameterType="int" resultMap="classesResultMap">
        select * from t_class where cid=#{cid}
    </select>
</mapper>

​ 修改持久化类:src/com/wsjy/pojo/Classes.java

public class Classes {
    private Integer cid;
    private String cname;
    private Integer studentNumber;
    private Date openingDate;
    
	//从表实体引用,实现一对多关系映射
    private List<Student> stus;

	/*setter/getter/toString*/
}

​ 修改映射器接口:src/com/wsjy/mapper/StudentMapper.java

public interface StudentMapper {
    ......
    /**
     * 查询所有cid相同的学生
     * @param cid
     * @return
     */
    List<Student> findStudentsByCid(int cid); 
}

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
	......
    <!-- 查询所有cid相同的学生 -->
    <select id="findStudentsByCid" parameterType="int" resultType="student">
        select * from t_student where cid=#{cid}
    </select>
</mapper>

​ 测试类:

	public static void main(String[] args) throws IOException {
        testFindClassByCid(2);
    }

    //根据班级id查询班级
    public static void testFindClassByCid(int cid){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        ClassesMapper mapper = sqlSession.getMapper(ClassesMapper.class);
        Classes cls = mapper.findClassByCid(cid);
        logger.debug(cls);
        logger.debug(cls.getStus());
        MyBatisUtil.closeSqlSession(sqlSession);
    }

​ 从测试方法运行的日志记录中可以看到执行了两条SQL语句,返回了一条班级信息和多条学生信息,此时级联查询实现成功。

多对多

​ MyBatis并不支持多对多级联,如果想要实现多对多级联,需要使用变通的方式:使用中间表。

​ 所谓中间表,就是将要实现多对多级联的A、B两张表的主键字段存放到一张表中,将这张表中的字段分别与A、B表的主键建立外键关系。当查询A表数据时,可以通过A表主键关联到中间表的外键字段,再通过中间表的B表的外键字段指向B表主键,从而实现级联。查询B表数据与查询A表过程一样,通过中间表进行关联。

​ 需求:查询科目信息时,同时将选修了该科目的所有学生的信息全部查询出来。

​ 在taotao中新增映射关系表t_student_subject:sid和sub_no,联合主键,添加数据。

​ 修改持久化类:src/com/wsjy/pojo/Subject.java

public class Subject {
    private Integer subNo;
    private String subName;
    
	//科目对学生:一对多关系映射
    private List<Student> stus;
    
    /*setter/getter/toString*/
}

​ 修改映射器接口:src/com/wsjy/mapper/SubjectMapper.java

public interface SubjectMapper {
	......
    /**
     * 根据科目编号查询科目信息,级联查询选修了该科目的所有学生信息
     * @param subNo
     * @return
     */
    Subject findSubjectBySubNo(int subNo);

    /**
     * 根据学号查询该学生选修的所有科目信息
     * @param sid
     * @return
     */
    List<Subject> findSubjectsBySid(int sid);
}

​ 修改映射器XML:src/com/wsjy/mapper/SubjectMapper.xml

<?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="com.wsjy.mapper.SubjectMapper">
	......
    <resultMap id="subjectResultMap" type="subject">
        <id property="subNo" column="sub_no"></id>
        <result property="subName" column="sub_name"></result>
        <collection property="stus" ofType="student"
                    select="com.wsjy.mapper.StudentMapper.findStudentsBySubNo" 		                         column="sub_no"/>
    </resultMap>
	<!-- 根据科目编号查询科目信息 -->
    <select id="findSubjectBySubNo" parameterType="int" resultMap="subjectResultMap">
        select * from t_subject where sub_no=#{sub_no}
    </select>
	<!-- 根据学号查询科目信息 
		SQL语句通过中间表进行查询,获取科目信息
		该方法在StudentMapper.xml中执行
	-->
    <select id="findSubjectsBySid" parameterType="int" resultMap="subjectResultMap">
        select * from t_subject s,t_student_subject tss
        where s.sub_no=tss.sub_no and tss.sid=#{sid}
    </select>
</mapper>

​ 修改持久化类:src/com/wsjy/pojo/Student.java

public class Student {
    private Integer sid;
    private String sname;
    private String ssex;
    private Integer sage;
    private String saddress;
    private Date sbirthday;
    private Integer cid;
	//学生对班级:一对一映射
    private Classes classes;
	//学生对科目:一对多映射
    private List<Subject> subs;
    
    /*setter/getter/toString*/
}

​ 修改映射器接口:src/com/wsjy/mapper/StudentMapper.java

public interface StudentMapper {
	......
    /**
     * 根据科目编号查询所有选修了该科目的学生信息
     * @param subNo
     * @return
     */
    List<Student> findStudentsBySubNo(int subNo);

    /**
     * 根据学生编号查询学生信息,级联查询该学生选修的所有科目信息、所属班级信息
     * @param sid
     * @return
     */
    Student findStudentBySid(int sid);
}

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
	......
    <resultMap id="studentResultMap" type="student">
        <id property="sid" column="sid"></id>
        <result property="sname" column="sname"></result>
        <result property="ssex" column="ssex"></result>
        <result property="sage" column="sage"></result>
        <result property="saddress" column="saddress"></result>
        <result property="sbirthday" column="sbirthday"></result>
        <result property="cid" column="cid"></result>
        <association property="classes" javaType="classes"
                     select="com.wsjy.mapper.ClassesMapper.findClassByCid" column="cid"/>
        <collection property="subs" ofType="subject"
                    select="com.wsjy.mapper.SubjectMapper.findSubjectsBySid" 								column="sid"/>
    </resultMap>
	<!-- 根据学号查询学生信息 -->
    <select id="findStudentBySid" parameterType="int" resultMap="studentResultMap">
        select * from t_student where sid=#{sid}
    </select>
	<!-- 根据科目编号查询学生信息 
		SQL语句通过中间表进行查询,获取学生信息
		该方法在SubjectMapper.xml中执行
	-->
    <select id="findStudentsBySubNo" parameterType="int" resultMap="studentResultMap">
        select * from t_student s,t_student_subject tss 
        where s.sid=tss.sid and tss.sub_no=#{sub_no}
    </select>
</mapper>

​ 测试类:

	public static void main(String[] args) throws IOException {
//        findSubjectBySubNo(2);
          findStudentBySid(2);
    }

    public static void findSubjectBySubNo(int subNo){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        SubjectMapper mapper = sqlSession.getMapper(SubjectMapper.class);
        Subject sub = mapper.findSubjectBySubNo(subNo);
        logger.debug(sub);
        logger.debug(sub.getStus());
        MyBatisUtil.closeSqlSession(sqlSession);
    }

    public static void findStudentBySid(int sid) throws InterruptedException {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student stu = mapper.findStudentBySid(sid);
        logger.debug(stu.getClasses().getCname());
        logger.debug(stu.getSubs().get(0));
        MyBatisUtil.closeSqlSession(sqlSession);
    }

鉴别器

​ 有时候,一个数据库查询可能会返回多个不同的结果集(总体上有一定联系的)。鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构,可以使用鉴别器来实现java的多态。鉴别器有点类似于java中的switch结构。

​ 需求:查询学生信息的同时级联查询学生的体检信息。

​ 分析:学生性别不同,体检项目也不同,因此体检信息表中记录的内容也不一样,在数据库中应创建两张体检表:男学生体检表和女学生体检表。

​ 假设男、女学生有相同的体检项目:心,肝,脾,肺,肾,男学生有独有的前列腺检查,女学生有独有的子宫检查。

​ 创建数据库表并添加数据:

  • t_male_health_form(男学生):id,heart,liver,spleen,lung,kidney,prostate,sid
  • t_female_health_form(女学生):id,heart,liver,spleen,lung,kidney,uterus,sid

​ 创建体检表父类:src/com/wsjy/pojo/HealthForm.java

public class HealthForm {
    private Integer id;
    private String heart;
    private String liver;
    private String spleen;
    private String lung;
    private String kidney;
    private Integer sid;
	
    /*setter/getter/toString*/
}

​ 创建男体检表类:src/com/wsjy/pojo/MaleHealthForm.java

public class MaleHealthForm extends HealthForm implements Serializable {
    private String prostate;

    /*setter/getter/toString*/
}

​ 创建女体检表类:src/com/wsjy/pojo/FemaleHealthForm.java

public class FemaleHealthForm extends HealthForm implements Serializable {
    private String uterus;

    /*setter/getter/toString*/
}

​ 创建男学生类:src/com/wsjy/pojo/MaleStudent.java

public class MaleStudent extends Student implements Serializable{
    private MaleHealthForm maleHealthForm;
    
    /*setter/getter/toString*/
}

​ 创建女学生类:src/com/wsjy/pojo/FemaleStudent.java

public class FemaleStudent extends Student implements Serializable{
    private FemaleHealthForm femaleHealthForm;
    
    /*setter/getter/toString*/
}

​ 创建男体检表映射器接口:src/com/wsjy/mapper/MaleHealthForm.java

public interface MaleHealthFormMapper {
    /**
     * 根据学生编号获取女学生体检表信息
     * @param sid
     * @return
     */
    MaleHealthForm getMaleHealthForm(int sid);
}

​ 创建男体检表映射器XML:src/com/wsjy/mapper/MaleHealthForm.xml

<?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="com.wsjy.mapper.MaleHealthFormMapper">
    <select id="getMaleHealthForm" resultType="maleHealthForm" parameterType="int">
        select * from t_male_health_form where sid=#{sid}
    </select>
</mapper>

​ 创建女体检表映射器接口:src/com/wsjy/mapper/FemaleHealthForm.java

public interface FemaleHealthFormMapper {
    /**
     * 根据学生编号获取女学生体检表信息
     * @param sid
     * @return
     */
    FemaleHealthForm getFemaleHealthForm(int sid);
}

​ 创建女体检表映射器XML:src/com/wsjy/mapper/FemaleHealthForm.xml

<?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="com.wsjy.mapper.FemaleHealthFormMapper">
    <select id="getFemaleHealthForm" resultType="femaleHealthForm" parameterType="int">
        select * from t_female_health_form where sid=#{sid}
    </select>
</mapper>

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
    <resultMap id="studentResultMap" type="student">
        <id property="sid" column="sid"></id>
        <result property="sname" column="sname"></result>
        <result property="ssex" column="ssex"></result>
        <result property="sage" column="sage"></result>
        <result property="saddress" column="saddress"></result>
        <result property="sbirthday" column="sbirthday"></result>
        <result property="cid" column="cid"></result>
        <association property="classes" javaType="classes" column="cid"
                     select="com.wsjy.mapper.ClassesMapper.findClassByCid" />
        <collection property="subs" ofType="subject" column="sid"
                    select="com.wsjy.mapper.SubjectMapper.findSubjectsBySid" />
        <!--配置鉴别器
			discriminator:
			column属性指定使用数据库表的哪个字段进行鉴别
			javaType属性是数据库字段对应的持久化类中属性的类型
		-->
        <discriminator javaType="int" column="ssex">
            <!--case
				value属性表示column字段查询得到的值
				resultMap表示查询结果使用哪个resultMap进行对象装配
			-->
            <case value="0" resultMap="femaleStudentmResultMap"/>
            <case value="1" resultMap="maleStudentResultMap"/>
        </discriminator>
    </resultMap>
	
    <!--
		男学生对应的resultMap
		需要注意的是extends属性,该属性用于设置当前对象的父类结果映射resultMap,而不能是父类的类型。
	-->
    <resultMap id="maleStudentResultMap" type="maleStudent" extends="studentResultMap">
        <!-- 使用一对一级联查询体检信息 -->
        <association property="maleHealthForm" javaType="maleHealthForm" column="sid"
                     select="com.wsjy.mapper.MaleHealthFormMapper.getMaleHealthForm"/>
    </resultMap>
	<!--
		女学生对应的resultMap
	-->
    <resultMap id="femaleStudentmResultMap" type="femaleStudent" extends="studentResultMap">
        <association property="femaleHealthForm" javaType="femaleHealthForm" column="sid"
                    select="com.wsjy.mapper.FemaleHealthFormMapper.getFemaleHealthForm"/>
    </resultMap>

    <select id="findStudentBySid" parameterType="int" resultMap="studentResultMap">
        select * from t_student where sid=#{sid}
    </select>

</mapper>

​ 运行测试类中的findStudentBySid(),根据学生性别的不同,级联查询了学生体检表。

立即加载和延迟加载

​ 立即加载:执行对主加载对象的查询时,马上执行对关联对象的select查询;

​ 延迟加载:执行对主加载对象的查询时,只查询主加载对象的信息,关联对象的查询会在关联对象被使用时才执行,也可被称为按需加载或懒加载;

​ 立即加载的优点是主加载对象和关联对象信息全部被加载到内存,需要使用时可以直接从内存中获取,方便快速,缺点是内存空间有限,如果关联表的信息不需要被使用也被加载到内存,会极大的浪费内存空间;

​ 延迟加载的优点是按需查询,不会浪费内存,缺点是会多次发起查询,增大数据库压力。

​ MyBatis默认立即加载,如需使用延迟加载,可在核心配置文件mybatis-config.xml中启用全局延迟加载开关。

​ 修改核心配置文件mybatis-config.xml

<!-- 开启全局延迟加载开关,true-开,false-关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 是否启用层级加载
     true表示启用,POJO的属性不论是否被使用都会被加载
     false表示不启用,POJO不使用的属性不被加载
     在3.4.1及之前的版本中默认为true,之后的版本中默认为false
-->
<setting name="aggressiveLazyLoading" value="false"/>

​ 执行测试类方法可以发现:当aggressiveLazyLoading值为true时,SQL语句执行的数量会比值为false时多。

​ 对于多表关联查询时,需要考虑被关联表信息的查询时机,也就是说,主表信息被查询时,关联表的信息应不应该同时被查询出来?

​ 实际应用中,根据表与表之间的关系,分为两种情况进行处理:

  • 一对多,选择延迟加载;
  • 一对一,选择立即加载;

​ 当需求中同时出现一对多和一对一时,通过全局延迟加载配置无法做到针对性处理,此时可以使用局部延迟加载策略:在<association>和<collection>中有fetchType属性,可以设置局部延迟加载策略,其取值有两个:lazy|eager。

​ 当设置了fetchType时,根据局部优先于全局的原则,该局部配置会覆盖全局设置。也就是说,就算是全局延迟加载已被启用,但某个局部设置了fetchType=“eager”,此时,该关联映射也会被立即加载。

sql

​ sql元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。

​ 需求:通过sql标签将重用率很高的SQL语句提取出来,实现一次书写,多次引用。

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
    ......
    <!-- 将公共部分提取出来 -->
    <sql id="queryStudent">
        select * from t_student
    </sql>
    
    <select id="findStudentBySid" parameterType="int" resultMap="studentResultMap">
        <!--需要使用公共部分时使用include引入-->
        <include refid="queryStudent"></include>
        where sid=#{sid}
    </select>

    <select id="findAll" resultType="student">
        <!--需要使用公共部分时使用include引入-->
        <include refid="queryStudent"></include>
    </select>

</mapper>

缓存操作

cache

​ MyBatis内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。默认情况下,只启用了本地的会话缓存(一级缓存:SqlSession),它仅仅对一个会话中的数据进行缓存。 MyBatis中也存在二级缓存(二级缓存:SqlSessionFactory),它被当前SqlSessionFactory对象所创建的所有会话(SqlSession)共享。

​ 缓存与核心组件SqlSession及SqlSessionFactory的生命周期相关,也就是说,SqlSession及SqlSessionFactory的生命周期决定了缓存的信息保存多久。

一级缓存

​ 一级缓存在于SqlSession中,缓存中的数据在以下几种情况中将会被清空:

  • SqlSession生命周期结束,也就是SqlSession被关闭时,一级缓存清空;
  • SqlSession对象执行了任意更新操作(增、删、改)时,一级缓存被清空;
  • SqlSession调用clearCache() ,主动清空一级缓存;
二级缓存

​ MyBatis默认二级缓存未开启,要启用全局的二级缓存,需要执行以下三步操作:

  1. 让MyBatis框架支持二级缓存,在核心配置文件的<settings>中配置。

    <!-- cacheEnabled默认为true,因此可以省略不配 -->
    <setting name="cacheEnabled" value="true"/>
    
  2. 让映射器支持二级缓存,在映射器XML文件中配置。

    <?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="com.wsjy.mapper.StudentMapper">
        <!--让映射器支持二级缓存-->
    	<cache/>
    	......
    </mapper>
    

    ​ <cache/>效果如下:

    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    ​ 缓存只作用于 cache 标签所在的映射文件中的语句。如果混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存,需要使用 @CacheNamespaceRef 注解指定缓存作用域。

  3. 让当前操作支持二缓存,在操作标签上配置。

    <select id="findStudentBySid" parameterType="int" 
            resultMap="studentResultMap" useCache="true">
        select * from t_student where sid=#{sid}
    </select>
    

测试二级缓存

​ 请注意:MyBatis不支持pojo的二级缓存,因此需要将之前的持久化类变为javaBean(让持久化类实现序列化接口java.io.Serializable即可),否则在执行二级缓存时会报异常。

	public static void main(String[] args) throws IOException {
        testSecondLevelCache();
    }

    public static void testSecondLevelCache(){
        SqlSession sqlSession1 = MyBatisUtil.createSqlSession();
        ClassesMapper mapper1 = sqlSession1.getMapper(ClassesMapper.class);
        Classes cls1 = mapper1.findClassByCid(3);
        logger.debug(cls1);

        MyBatisUtil.closeSqlSession(sqlSession1);//清空一级缓存

        SqlSession sqlSession2 = MyBatisUtil.createSqlSession();
        ClassesMapper mapper2 = sqlSession2.getMapper(ClassesMapper.class);
        Classes cls2 = mapper2.findClassByCid(3);
        logger.debug(cls2);

        MyBatisUtil.closeSqlSession(sqlSession2);
        logger.debug(cls1==cls2);
    }

​ 上面的测试可以正常运行,并且对应的SQL语句只执行一次,从而验证了二级缓存的存在。但请注意,虽然SQL只执行了一次,两次创建的对象内存地址不相同(此处已将实体类的toString()删除),进行“==”比较时返回的也是false。

​ 为什么会出现这种情况?

​ 原因是MyBatis的二级缓存中缓存的不是对象,而是对象中保存的数据。当SqlSessionFactory创建SqlSession时,会将二级缓存中的数据拿出来,创建新的对象交给SqlSession使用,不同的SqlSession中拿到的都是重新创建的对象,因此对象肯定不相同,但不同对象中保存的数据是相同的。

cache元素常用属性

eviction(缓存清除策略)属性:用于设置缓存清除策略,可选的值有LRU、FIFO、SOFT、WEAK,默认LRU。

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

flushInterval(刷新间隔)属性:可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性:可以被设置为任意正整数,要注意想缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性:可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改,这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝, 速度上会慢一些,但是更安全,因此默认值是 false。

cache-ref

​ 对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 如果想要在多个命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另一个缓存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

动态SQL

​ 所谓SQL的动态和静态,是指SQL语句在何时被编译和执行。

静态SQL和动态SQL的区别

  • 静态SQL,在编译阶段就可以确定数据库要做什么事情,也就是说SQL语句的结构在程序运行之前就已经固定,这样的SQL称为静态SQL。

  • 动态SQL,在编译阶段无法确定SQL结构,只有等到程序运行起来,根据某些运行期间得到的信息才能决定SQL语句结构,这种SQL叫做动态SQL。

​ 请注意:静态和动态是根据在程序运行前SQL语句结构是否固定来进行判定的。

##静态SQL
##程序运行前,结构固定,与参数值是否固定无关
select * from t_student where sid=5;
select * from t_student where sid=?;
##动态SQL
##程序运行前,结构不固定。
##比如where后的条件,有可能根据sid进行查询,也有可能根据sname查询,还有可能根据其他字段查询
##注意,下面SQL中的问号只是为了说明where之后的结构在程序运行前并不确定,只有在程序运行之后才能决定
select * from t_student where ?;
常用动态SQL标签
<if>

​ 使用动态 SQL 最常见场景是根据条件包含 where 子句的一部分。

​ 修改映射器接口:src/com/wsjy/mapper/StudentMapper.java

public interface StudentMapper {
	......
    /**
     * 根据某些条件查询学生信息
     * @param student
     * @return
     */
    List<Student> findBySomeCondition(Student student);
}

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
	......
    <!-- 程序运行时,会根据<if>标签的test属性检测结果,动态实现SQL拼装
	如果传入的参数student能被以下3个if的test通过检测,拼接出来的SQL语句如下:
	select * from t_student where 1=1 and sid=#{sid} and sname=#{sname} and ssex=#{ssex}
	哪一个test没通过检测,SQL语句就缺少哪一部分。
 	-->
    <select id="findBySomeCondition" parameterType="student" resultType="student">
        <!-- where 1=1,是为了让SQL拼接不出错。
		因为<if>中写的是and,如果没有1=1,SQL会拼接成where and,导致出错而无法运行
		如果<if>中不加and,那么多个条件时,SQL会拼接成where 条件1 条件2,同样会出现错误
		-->
        select * from t_student where 1=1
        <!-- test属性使用的是持久化类的属性 -->
        <if test="sid!=null">
             and sid=#{sid}
        </if>
        <if test="sname!=null">
            and sname=#{sname}
        </if>
        <if test="ssex!=null">
            and ssex=#{ssex}
        </if>
    </select>
</mapper>

​ 测试类:

	public static void main(String[] args) throws IOException {
        Student student=new Student();
        //student.setSid(3);
        //student.setSname("钱三");
        student.setSsex("男");
        student.setSage(30);
        student.setSaddress("伦敦");
        student.setSbirthday(Date.valueOf("2020-1-1"));
        student.setCid(3);

        findBySomeCondition(student);
    }

    public static void findBySomeCondition(Student student){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> stus = mapper.findBySomeCondition(student);
        logger.debug(stus);
        MyBatisUtil.closeSqlSession(sqlSession);
    }
<where>

​ 当不希望在SQL语句中出现where 1=1这个恒等条件时,可以使用<where>标签来解决这个问题。

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
	......
    <select id="findBySomeCondition" parameterType="student" resultType="student">
        select * from t_student
        <!-- <where>标签会自动去除第一个<if>标签中的and,确保SQL拼接正确 -->
        <where>
            <if test="sid!=null">
                and sid=#{sid}
            </if>
            <if test="sname!=null">
                and sname=#{sname}
            </if>
            <if test="ssex!=null">
           		and ssex=#{ssex}
        	</if>
        </where>
    </select>
</mapper>
<choose>、<when>、<otherwise>

​ 当不想使用所有的条件,而只是想从多个条件中选择一个使用时,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句,<when>相当于switch语句的case,<otherwise>相当于switch语句的default。

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
	......
    <select id="findBySomeCondition" parameterType="student" resultType="student">
        select * from t_student
        <where>
            <!-- 
 				choose会选择第一个有值的when作为条件使用,如果when都不满足,使用otherwise
			-->
            <choose>
                <when test="sid!=null">
                    and sid=#{sid}
                </when>
                <when test="sname!=null">
                    and sname=#{sname}
                </when>
                <otherwise>
                    and ssex=#{ssex}
                </otherwise>
            </choose>
        </where>
    </select>
</mapper>
<set>

​ 用于动态更新语句的解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

​ 修改映射器接口:src/com/wsjy/mapper/StudentMapper.java

public interface StudentMapper {
	......
    /**
     * 更新学生某些信息
     * @param student
     */
    void updateStudentBySomeInformation(Student student);
}

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
	......
    <update id="updateStudentBySomeInformation" parameterType="student">
        update t_student
        <!-- 通过set来动态设置更新的参数,set会自动去除后缀“,” -->
        <set>
            <if test="sname!=null">
                sname=#{sname},
            </if>
            <if test="ssex!=null">
                ssex=#{ssex},
            </if>
            <if test="sage!=null">
                sage=#{sage},
            </if>
        </set>
        where sid=#{sid}
    </update>
</mapper>

​ 测试类:

	public static void main(String[] args) throws IOException {
        Student student=new Student();
        student.setSid(1);
        student.setSname("武则天");
        student.setSsex("女");
        student.setSage(30);
        student.setSaddress("伦敦");
        student.setSbirthday(Date.valueOf("2020-1-1"));
        student.setCid(3);

        updateStudentBySomeInformation(student);
    }

    public static void updateStudentBySomeInformation(Student student){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        mapper.updateStudentBySomeInformation(student);
        MyBatisUtil.closeSqlSession(sqlSession);
    }
<trim>

​ trim标签是一个格式化标签,可以用来代替set或者where标签的功能。

	<select id="findBySomeCondition" parameterType="student" resultType="student">
        select * from t_student
        <!-- 
			使用trim替换where,需要设置前缀prefix,
			prefixOverrides属性忽略第一个满足条件的if或when中的"and |or" 
		-->
        <trim prefix="where" prefixOverrides="and |or">
            <choose>
                <when test="sid!=null">
                    and sid=#{sid}
                </when>
                <when test="sname!=null">
                    and sname=#{sname}
                </when>
                <otherwise>
                    and ssex=#{ssex}
                </otherwise>
            </choose>
        </trim>
    </select>

	<update id="updateStudentBySomeInformation" parameterType="student">
        update t_student
        <!-- 
			使用trim替换set,需要设置前缀prefix,
			suffixOverrides属性忽略最后一个的if中的“,” 
		-->
        <trim prefix="set" suffixOverrides=",">
            <if test="sname!=null">
                sname=#{sname},
            </if>
            <if test="ssex!=null">
                ssex=#{ssex},
            </if>
            <if test="sage!=null">
                sage=#{sage},
            </if>
        </trim>
        where sid=#{sid}
    </update>
<foreach>

​ 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

​ 修改映射器接口:src/com/wsjy/mapper/StudentMapper.java

public interface StudentMapper {
	......
    /**
     * 查询sid存在于某个集合中的所有学生信息
     * @param sids
     * @return
     */
    List<Student> findBySids(List<Integer> sids);
}

​ 修改映射器XML:src/com/wsjy/mapper/StudentMapper.xml

<?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="com.wsjy.mapper.StudentMapper">
	......
    <select id="findBySids" parameterType="list" resultType="student">
        select * from t_student
        where sid in
        <!-- 
			item为集合中实体名,自定义,#{}中的名称必须与item一致,
			index指定一个名字,用于表示在迭代过程中,每次迭代到的位置
			collection表示当前迭代的集合类型,只接受一个参数:
			如果传入参数为单个时:集合使用list,数组使用array,Map使用map
			如果传入参数为多个时:只能封装成Map,使用map
			open属性:表示调用的sql语句前缀添加的内容
			close属性:表示调用的sql语句后缀添加的内容
			separator属性:分隔符,表示每一次迭代元素之间用什么分隔
		-->
        <foreach item="sid" index="index" collection="list"
                 open="(" separator="," close=")">
            #{sid}
        </foreach>
    </select>
</mapper>

​ foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。

​ 可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach

​ 当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

​ 测试类:

public static void main(String[] args) throws IOException {
        List<Integer> items=new ArrayList<>();
        items.add(1);
        items.add(2);
        items.add(3);
        items.add(4);
        items.add(5);

        findBySids(items);
    }

    public static void findBySids(List<Integer> sids){
        SqlSession sqlSession = MyBatisUtil.createSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> stus = mapper.findBySids(sids);
        logger.debug(stus);
        MyBatisUtil.closeSqlSession(sqlSession);
    }

使用注解开发MyBatis应用

​ MyBatis支持注解开发,可使用注解替代映射器XML文件,但对于同一个映射器接口不能既用注解又用映射器XML文件,否则会导致MyBatis无法确定到底使用哪种方式而报错。

CURD操作

​ MyBatis实现CURD操作有对应的注解:@Select,@Update,@Insert,@Delete。

​ 需求:使用注解对t_student进行CURD操作。

​ 创建映射器接口:src/com/wsjy/mapper/StudentMapper2.java

public interface StudentMapper2 {
    /**
     * 查询所有学生信息
     * @return
     */
    @Select("select * from t_student")
    List<Student> findAll();
    
    /**
     * 根据学号查询学生信息
     * @return
     */
    @Select("select * from t_student where sid=#{sid}")
    Student findStudentBySid(int sid);
    
    /**
     * 新增学生
     * @param student
     */
    @Insert("insert into t_student values(#{sid},#{sname},#{ssex},#{sage},#{saddress},#{sbirthday},#{cid})")
    void addStudent(Student student);

    /**
     * 根据学号删除学生信息
     * @param sid
     */
    @Delete("delete from t_student where sid=#{sid}")
    void deleteStudentBySid(int sid);

    /**
     * 根据学号修改学生信息
     * @param student
     */
    @Update("update t_student set sname=#{sname},ssex=#{ssex} where sid=#{sid}")
    void updateStudent(Student student);
  
}

​ 在测试类中运行以上方法都可正常执行,从运行结果来看,在查询学生信息时,对应的班级和科目信息为null,代表并没有级联查询班级和科目,可使用注解实现级联查询。

级联

​ MyBatis通过@Results,@Result,@One,@Many来实现级联操作。

  • @Results:<resultMap>
  • @Result:<result>,通过id属性来区分<id>(true)和<result>(false或省略不写)
  • @One:<association>
  • @Many:<collection>

​ 需求:在查询学生信息的同时,级联查询班级和科目信息

​ 修改映射器接口:src/com/wsjy/mapper/StudentMapper2.java

public interface StudentMapper2 {
	......
  	/**
     * 根据学生编号查询学生信息,级联查询该学生选修的所有科目信息、所属班级信息
     * @param sid
     * @return
     */
    @Select({"select * from t_student where sid=#{sid}"})
    @Results(id = "studentMap",value = {
            @Result(id = true,property = "sid",column = "sid"),
            @Result(property = "sname",column = "sname"),
            @Result(property = "ssex",column = "ssex"),
            @Result(property = "sage",column = "sage"),
            @Result(property = "saddress",column = "saddress"),
            @Result(property = "sbirthday",column = "sbirthday"),
            @Result(property = "cid",column = "cid"),
            @Result(property = "classes",column = "cid",
                    one = @One(select = "com.wsjy.mapper.ClassesMapper.findClassByCid",
                            fetchType = FetchType.EAGER)),
            @Result(property = "subs",column = "sid",
                    many = @Many(select = "com.wsjy.mapper.SubjectMapper.findSubjectsBySid",
                            fetchType = FetchType.LAZY))
    })
    Student findStudentBySid(int sid);
}

​ 鉴别器也可以使用注解:@TypeDiscriminator,@Case来完成。

​ 从以上案例可以看出,使用注解实质上并没有减少任何配置,只是将映射器XML文件中的配置全部转移到java源文件中来而已,同时造成代码结构混乱,可读性差,SQL语句与java代码严重耦合,程序功能一旦有所变化,必须修改java源码等一系列问题。

​ 因此在实际应用开发中,大多数公司其实是禁止使用注解开发的。

缓存

​ 缓存主要是指二级缓存,因为一级缓存自动开启,可以直接使用。使用注解开启二级缓存,非常简单,只需要在映射器接口上使用@CacheNamespace(blocking = true)即可开启。

@CacheNamespace(blocking = true)
public interface StudentMapper2 {
	......
}

动态SQL

​ mybatis3中增加了使用注解来配置Mapper的新特性,常用的注解有:@SelectProvider、@UpdateProvider、@InsertProvider和@DeleteProvider。

​ 以上注解用于Mapper接口的方法上,使用type属性来指定由哪个类来提供SQL语句,使用method属性指定由type属性指定的类中哪个具体的方法提供SQL。

示例:

 public interface UserMapper {
      //指定被@SelectProvider注解的方法使用SqlProvider类的selectUser方法提供的SQL语句
     @SelectProvider(type = SqlProvider.class, method = "selectUser")
     @ResultMap("userMap")
     public User getUser(long userId);
 }

​ @SelectProvider注解用于生成查询用的sql语句,有别于@Select注解,@SelectProvide指定一个Class及其方法,并且通过调用Class上的这个方法来获得sql语句。在上例中,获取查询sql的方法是SqlProvider.selectUser。

​ @ResultMap注解用于从查询结果集RecordSet中取数据然后拼装实体bean。

SqlProvider.java

 public class SqlProvider {
     public String selectUser(long userId) {
         return "select * from user where userId=" + userId;
     }
 }

也可使用以下形式:

SqlProvider.java

public class SqlProvider{
    /**
     * list类型:mapper方法形参和sql构建器方法形参必须同时加@Param注解
     * @param ids
     * @return
     */
    public String deleteByIdsSql(@Param("ids") List<Integer> ids){
        return new SQL(){{
            DELETE_FROM("t_test");
            String idStr="";
            for (int i = 0; i < ids.size(); i++) {
                if (i<ids.size()-1)
                    idStr+=ids.get(i)+",";
                else
                    idStr+=ids.get(i);
            }
            WHERE("id in ("+idStr+")");
        }}.toString();
    }

    /**
     * 单参数:sql构建器方法参数上同时加上@Param注解,
     * 单参数时,mapper方法参数上可不加@Param注解,但建议加上
     * @return
     */
    public String selectByIdsSql(@Param("id") Integer id){
        return new SQL()
                .SELECT("*")
                .FROM("t_goods")
                .WHERE("id="+id)
                .toString();
    }
}

使用第二种方式的优势是将SQL语句进行结构化,层次分明,避免了第一种形式书写复杂SQL时不便于阅读的问题,具体规则可参见mybatis官网: https://mybatis.org/mybatis-3/zh/statement-builders.html

mybatis逆向工程及分页插件的使用

​ 为了提高开发效率,可以使用mybatis逆向工程来快速生成映射接口、映射XML文件以及实体类。(此处使用maven完成项目构建)

一、使用main函数完成代码生成

步骤1:导入依赖。

	<dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
    </dependency>
	<!--逆向工程-->
    <dependency>
      <groupId>org.mybatis.generator</groupId>
      <artifactId>mybatis-generator-core</artifactId>
      <version>1.3.7</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.43</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.3</version>
    </dependency>
	<!--分页插件-->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.8</version>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.18</version>
    </dependency>

步骤2:编写配置。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <context id="mysqlTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!-- Mysql数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/rbac?characterEncodign=UTF-8&amp;useSSL=false"
                        userId="root"
                        password="root">
        </jdbcConnection>

        <!-- 默认为false,把JDBC DECIMAL 和NUMERIC类型解析为Integer,为true时
        把JDBC DECIMAL 和NUMERIC类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- targetProject:生成POJO类的位置 -->
        <javaModelGenerator targetPackage="com.woniuxy.domain" targetProject=".\src\main\java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="com/woniuxy/mapper" targetProject=".\src\main\resources">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <!--
            targetProject:mapper接口生成的的位置
            type:使用哪种方式实现接口方法
            XMLMAPPER:生成xml文件
            ANNOTATEDMAPPER:使用注解
         -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.woniuxy.mapper" targetProject=".\src\main\java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>


        <!-- 指定数据表 -->
        <!-- schema为数据库名,oracle需要配置,mysql不需要配置。
             tableName为对应的数据库表名
             domainObjectName 是要生成的实体类名(可以不指定)
             enableXXXByExample 默认为 true, 为 true 会生成一个对应Example帮助类,帮助你进行条件查询 false不生成
        -->
        <table schema="" tableName="t_user">
            <domainObjectRenamingRule searchString="^T" replaceString=""/>
        </table>
        <table schema="" tableName="t_role">
            <domainObjectRenamingRule searchString="^T" replaceString=""/>
        </table>
        <table schema="" tableName="t_permission">
            <domainObjectRenamingRule searchString="^T" replaceString=""/>
        </table>

    </context>
</generatorConfiguration>

步骤3:自动代码生成的工具类。

package com.woniuxy.utils;

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;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

/**
 * 自动生成代码工具类
 */
public class GeneratorUtil {

    public static void generator() throws Exception{
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = false;//是否覆盖生成的文件
        // 指定配置文件
        File configFile = new File(GeneratorUtil.class.getClassLoader().getResource("generatorConfig.xml").toURI());
        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 {
            GeneratorUtil.generator();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

步骤4:创建mybatis配置文件sqlConfig.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>
    <!--配置分页插件-->
    <plugins>
    	<!-- com.github.pagehelper为PageHelper类所在包名 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 指定数据库类型 -->
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>
    <!-- 配置项目开发期间的运行环境,指定事务处理方式和数据库连接信息,环境可以配置多个。
 		<environments>标签的default属性指向哪个<environment>标签的id属性,哪个环境就被使用。
	-->
    <environments default="mysql">
        <environment id="mysql">
            <!-- 配置事务管理器 -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源,<dataSource>的type属性可取值:POOLED、UNPOOLED、JNDI
 				POOLED:使用连接池
				UNPOOLED:不使用连接池
				JNDI:使用JNDI
			-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql:///rbac?characterEncoding=UTF-8&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置映射器路径 -->
    <mappers>
        <!-- 常见第一种引入映射器XML,通过文件路径引入 
		<mapper resource="com/wsjy/mapper/StudentMapper.xml"/>-->
        <!-- 常见第二种引入映射器接口,通过包名引入,将对应包下的所有映射器接口都引入 -->
        <package name="com.woniuxy.mapper"/>
        <!-- 常见第三种引入某个具体的映射器接口,通过类注册引入,使用类的全限定名 
		<mapper class="com.wsjy.mapper.StudentMapper"/>-->
    </mappers>
</configuration>

步骤5:测试。

public class Test {
    public static void main(String[] args) throws IOException 
        //使用分页插件,查询第二页的5条记录
        PageHelper.startPage(2,5);
        SqlSession sqlSession = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlConfig.xml")).openSession();
        PermissionMapper permissionMapper = sqlSession.getMapper(PermissionMapper.class);
    	//查询全部,不带条件
        List<Permission> permissions = permissionMapper.selectByExample(null);
        System.out.println(permissions);
    }
}

二、使用maven插件完成代码生成

​ 用maven插件替换上例中的第三个步骤即可。

<build>
    <plugins>
      <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.7</version>
        <configuration>
          <verbose>true</verbose>
          <!--是否覆盖之前生成的文件-->
          <overwrite>true</overwrite>
          <!--逆向工程配置文件所在路径-->
          <configurationFile>
            src/main/resources/generatorConfig.xml
          </configurationFile>
        </configuration>
        <!--导入逆向工程插件需要使用的依赖-->
        <dependencies>
          <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-core</artifactId>
            <version>1.3.7</version>
          </dependency>
          <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.43</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>

​ 如果plugin中的依赖在dependencies中被导入,可以使用<includeCompileDependencies>标签引入即可,从而不必在插件中再使用dependencies重新导入对应依赖。

<build>
    <plugins>
      <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.7</version>
        <configuration>
          <verbose>true</verbose>
          <!--是否覆盖之前生成的文件-->
          <overwrite>false</overwrite>
          <configurationFile>
            src/main/resources/generatorConfig.xml
          </configurationFile>
          <!--引入dependencies中导入的依赖-->
          <includeCompileDependencies>true</includeCompileDependencies>
        </configuration>
      </plugin>
    </plugins>
  </build>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值