MyBatis框架安排

1. 第一天:

1. MyBatis入门

  1. 什么是框架?
    答:它是我们软件开发过程中的一套解决方案, 不同的框架解决不同的问题。
    简而言之,框架其实就是某种应用的半成品,就是一组组件,供你选用完成你自己的系统。简单说就是使用别 人搭好的舞台,你来做表演。而且,框架一般是成熟的,不断升级的软件。
  2. 三层框架
    1. 表现层:control
    2. 业务逻辑层:service
    3. 持久层:dao


可以看到不同的框架各司其职,在软件开发中担任不同的角色,MyBatis框架主要是与数据库打交道,所以它工作在持久层。

  1. 持久层的传统解决方案
    1. JDBC技术
      1. Connection:连接数据库对象
      2. PrepareStatement:预处理对象,用于操作sql
      3. ResultSet:操作数据库后生成的结果集
    2. Spring的SpringTemplate技术
      Spring中对jdbc的简单封装
    3. Apache的DBUtils
      和SpringTemplate类似,Apache中对jdbc的简单封装

以上所用都不是框架
JDBC是接口规范
SpringTemplate和DBUtils都是对jdbc接口规范的实现类,是一个工具类。

传统的jdbc访问数据库:

public static void main(String[] args) {    
	Connection connection = null;    
	PreparedStatement preparedStatement = null;    
	ResultSet resultSet = null;   
	 try {     
		 	//加载数据库驱动     
			Class.forName("com.mysql.jdbc.Driver");     
			 //通过驱动管理类获取数据库链接    
			connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8","ro ot", "root"); 
		    //定义 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) {      
			      e.printStackTrace();    
			        }   
		          }   
		        if(preparedStatement!=null){    
		              try {     
		                preparedStatement.close();   
		                   } catch (SQLException e) {   
		                       e.printStackTrace();  
		                           }   
		                             }     
		        if(connection!=null){ 
	                   try {    
	                      connection.close();    
	                        } catch (SQLException e) {   
	                            // TODO Auto-generated catch block      
	                             e.printStackTrace(); 
	                        }     
	                      	  }  
	                              }  
		                               } 
上边使用 jdbc 的原始方法(未经封装)实现了查询数据库表记录的操作。 

PreparedStatement对象它的执行方法

  1. execute:可以执行任何CRUD语句,返回值是一个布尔值,表示是否有结果。
  2. excuteUpdate:只能执行CUD,查询语句无法执行,返回值数据库操作影响的行数。
  3. excuteQuery:只能执行Select 语句,无法进行增删改操作,执行的结果集为ResultSet对象

2. MyBatis概述

  1. 概念:mybatis是一个持久层框架,用Java编写,它封装了jdbc的很多细节,使开发者能够只关注sql语句本身,无需关注注册驱动,创建连接等繁琐的过程,它使用了ORM思想实现了结果集的封装。
    ORM思想:
    Object Relational Mapping对象关系映射
    简单的说就是实体类的属性和数据库表的字段产生了对应关系,使得我们操作实体类就可以操作数据库表了。
    例如:

    user表 User类
    username varchar(32) String username
    id int(12) int id
    grade varchar(12) String grade

3. MyBatis环境搭建

  1. 环境搭建步骤:

    1. 创建maven工程并导入坐标
    2. 创建实体类和dao接口
    3. 创建MyBatis的主配置文件:SqlMapConfig.xml
    4. 创建dao层映射配置文件:IUserDao.xml
  2. 注意事项:

    1. 映射配置文件还有一种写法是Mapper.xml。我们写成IUserDao.xml主要是为了和之前写过的dao做一个过渡。
    2. 在里面创建目录时,它和创建包是不一样的。例如
      创建目录:com.liuzeyu.dao 它只是一级目录,在硬盘上也是一级目录
      创建包:com.liuzeyu.dao 它只是三级目录,分层显示,在硬盘上也是三级目录
    3. mybatis的映射配置文件必须和dao层的包结构相同,映射配置文件写在resource资源文件下
    4. 映射配置文件的mapper标签下的namespace属性取值必须是dao层接口的全限定类名
    5. 映射配置文件的操作配置,如select的id属性取值必须是dao层接口的方法名

    当我们遵从了3,4,5点后,就可以不用像从前那样写dao接口的实现类了。

4. MyBatis入门案例

  1. 在搭建完环境后,完成入门案例。首选创建实体类和MySQL数据库和表,并生成相应的getter和setter方法

    sql代码:

    DROP TABLE IF EXISTS `user`;
    
    CREATE TABLE `user` (
      `id` int(11) NOT NULL auto_increment,
      `username` varchar(32) NOT NULL COMMENT '用户名称',
      `birthday` datetime default NULL COMMENT '生日',
      `sex` char(1) default NULL COMMENT '性别',
      `address` varchar(256) default NULL COMMENT '地址',
      PRIMARY KEY  (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    
    insert  into `user`(`id`,`username`,`birthday`,`sex`,`address`) values (41,'老王','2018-02-27 17:47:08','男','北京'),(42,'小二王','2018-03-02 15:09:37','女','北京金燕龙'),(43,'小二王','2018-03-04 11:34:34','女','北京金燕龙'),(45,'传智播客','2018-03-04 12:04:06','男','北京金燕龙'),(46,'老王','2018-03-07 17:37:26','男','北京'),(48,'小马宝莉','2018-03-08 11:44:00','女','北京修正');
    
    
  2. 项目对象模型(pom.xml)

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>com.liuzeyu</groupId>
        <artifactId>day01_mybatis</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
        <dependencies>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.5</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.10</version>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.12</version>
            </dependency>
        </dependencies>
    </project>
    
  3. dao层接口

    package com.liuzeyu.dao;
    import com.liuzeyu.domain.User;
    import java.util.List;
    
    public interface IUserDao {
        public List<User> findAll();
    }
    
    
  4. SqlMapConfig.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">
    <!-- mybatis主配置文件 -->
    <configuration>
        <!-- 配置环境 -->
        <environments default="mysql">
            <!-- 配置mysql环境 -->
            <environment id="mysql">
                <!-- 配置事物类型 -->
                <transactionManager type="JDBC"></transactionManager>
                <!-- 配置数据库源(连接池)-->
                <dataSource type="POOLED">
                    <!-- 配置连接数据库的四个基本信息 -->
                    <property name="driver" value="com.mysql.jdbc.Driver" />
                    <property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis" />
                    <property name="username" value="root" />
                    <property name="password" value="809080" />
                </dataSource>
            </environment>
        </environments>
    
        <!-- 指定映射配置文件的位置,映射配置文件是指每一个独立的dao接口配置-->
        <mappers>
            <mapper resource="com.liuzeyu.dao.IUserDao.xml"/>
        </mappers>
    </configuration>
    
  5. 映射文件IUserDao.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.liuzeyu.dao.IUserDao">
        <select id="findAll">
            SELECT * from user
        </select>
    </mapper>
    

  1. 编写测试类MyBatisTest.java

    public class MyBatisTest {
        /**
         * mybatis入门案例测试
         * @param args
         */
        public static void main(String[] args) throws Exception{
            //1.读取配置文件SqlMapConfig.xml
            InputStream is = MyBatisTest.class.getClassLoader().getResourceAsStream("SqlMapConfig.xml");
            //2.创建SqlSessionFactory工厂
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(is);
            //3.使用factory创建SqlSession
            SqlSession session = factory.openSession();
            //4.使用SqlSessionFactory创建dao层接口的动态代理对象
            IUserDao dao = session.getMapper(IUserDao.class);
            //5.使用代理对象执行方法
            List<User> users = dao.findAll();
            //6.遍历输出结果
            for (User user : users) {
                System.out.println(user);
            }
            //释放资源
            is.close();
            session.close();
        }
    }
    
    
  2. 运行后出现

    翻译过来,没有找到IUserDao.xml,考虑到映射文件是在SqlMapConfig.xml上配置的,故检查SqlMapConfig.xml
    发现:

    改成:

    再次运行又发现:

    这个问题是未在接口的映射配置文件中定义resultType导致的,resultType是操作数据库返回封装结果集的对象

    添加后:

  3. 测试输出:

    设计到的设计模式分析(涉及到构建者模式,工厂模式,代理模式简单看一下,下面还会学习到)

  4. 查看测试类方法,发现代码也比较复杂,涉及到多个对象,如SqlSessionFactoryBuilder,SqlSessionFactory…其实MyBat也提供了更为简洁的实习方式,即,注解配置,这样虽然不能减少测试类的代码编写,但是可以直接删除掉映射接口的配置文件,采用注解配置,将原来的配置文件删除

    重新在接口的定义处添加

    感觉简单了很多,少了配置文件,但是需要在Mybatis的主配置文件中修改映射文件的配置,将resource属性改为class属性,并添加接口的全限定类名

    重新运行程序:

  5. 其实MyBatis也提供了接口实现类的方法来与数据库交互,只需要对dao层添加接口实现类,可以在9的基础上进行改造
    添加dao层的接口实现:

    测试函数部分就可以省去SqlSession对象的建立

    小结:
    通过学习mybatis的案例可以发现,比我们之前使用的jdbc简单很多,可以省去接口实现类和jdbc初始化等一系列操作,添加两个配置文件即可,再者可以通过注解配置,使得与数据库的交互更加的简单。
    但是这里面包含了很多的细节,为什么会有工厂对象SqlSessionFactory,为什么有了工厂对象后还需要有构建者对象SqlSessionFactoryBuilder,为什么IUserDao.xml 在创建的时候有文件名和位置的规定。通过下一章的学习可以明白mybatis的内部执行流程,并且可以对这些设计模式有一个认识。

<mark>注意事项</mark>

  1. 不要忘记在接口的映射配置文件中告知mybatis要封装到哪个实体类中,配置的方式:指定实体类的全限定类名。
  2. 如果使用注解配置,需要在主配置文件处修改mapper属性为class属性

5. 自定义MyBatis框架(深入了解MyBatis执行细节)

  1. 分析在上述案例中的测试类selectList是如何运行的?

    List< E > selectList(PreparedStatement statement)
    当代理对象过程中,调用了Proxy.newInstanceProxy(类加载器,代理对象要实现的接口字节码数组,增强代码),在增强代码部分进行selectList的调用。
    先不谈代理模式,来看看selectList都是怎么进行的呢?

    1. 首先程序通过xml解析技术对SqlMapConfig.xml进行读取,获取到4个连接数据库的必备信息和映射接口配置文件的路径
    2. 有了driver,url,username,password就可以在selectList中创建Connection对象
    3. 通过配置文件的映射关系可以得到真正执行的sql语句和结果集的全限定类名。
    4. 有了sql就可以获取到PreparedStatement 的preparedStatement对象了,prepareStatement = connection.prepareStatement(sql);
    5. 有了结果集的全限定类名接口就可以通过反射机制ClassforName(“全限定类名”).newInstance()得到真实的返回值类型对象
    6. 有了4,5两对象就可以通过遍历结果集来将结果存入准备好的list集合中并返回

    问题:
    基于4,5,如何将

    <mapper namespace="com.liuzeyu.dao.IUserDao">
        <select id="findAll" resultType="com.liuzeyu.domain.User">
            SELECT * from user
        </select>
    </mapper>
    

    发送到selectList,需要将映射信息封装成一个Mapper对象,这是一个Map对象

    key(String) value(Mapper)
    com.liuzeyu.dao.findAll 全限定类名+sql语句

    这样做的好处是,如果以后会有多个查询findById,findByName等一系列方法可以直接和全限定类名找到对应关系。

  2. 问题2:内部的SqlSession创建代理对象的执行流程

    //4.使用SqlSessionFactory创建dao层接口的动态代理对象
    IUserDao dao = session.getMapper(IUserDao.class);
    

    核心是通过getMapper(IUserDao.class);来获取代理对象的

public <T> getMapper(Class<T> daoInterfaceClass){
	/**
		类加载器:它使用的和被代理对象相同的类加载器
		代理对象要实现的接口:它使用的和被代理对象实现相同的接口
		如何代理:增强方法,需要我们字节来提供
		此处是一个InvocationHandler的接口,我们需要写一个该接口的实现类,在实现类中调用selectList方法
	*/
	Proxy.newProxyInstance(类加载器,代理对象要实现的接口字节码数组,如何代理);
}
  1. 自定义代理模式
    1. 分析:

      1. mybatis使用dao代理的方式实现增删改操作时做什么事?
        两件事:
        1. 创建代理对象
        2. 在代理对象中调用selectList
    2. 自定义mybatis 所需要的类
      1. Class Resource
      2. Class SqlSessionFactoryBuilder
      3. interface SqlSessionFactory
      4. interface SqlSession

    3. 由于涉及的代码较为复杂,故将项目up到git上面

      https://github.com/liuzeyu12a/mybatis-