SSM三大框架之MyBatis总结【动力节点老杜】

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。

终于把MyBatis学完了 下一个Spring 希望早日学完 好想拥有自己的网站呀


JDBC的缺点


1.SQL语句写死在java程序中,如果需要修改SQL语句,就要改java代码,违背OCP原则


。OCP原则:软件实体(包括类、模块、功能等)应该对扩展开放,但是对修改关闭


2.JDBC代码繁琐且重复


比如获取值、创建对象、给对象的属性赋值


MyBatis


一、基础概念


  • 本质上就是对JDBC的封装,完成CRUD的操作


  • 属于持久层框架


  • ORM


。Object:JVM中的java对象

。Relational:关系型数据库

。Mapping:映射,将java虚拟机中的java对象映射到数据库表中一行记录,或是将数据库表中一行记录映射成Java虚拟机中的一个Java对象


1c3e63a52023e633dbe7c234f3ea4b42.png


。MyBatis是一个半自动化的ORM框架,因为SQL语句是需要自己编写

。Hibernate是一个全自动化的ORM框架


  • MyBatis框架特点


。支持定制化SQL、存储过程、基本映射以及高级映射->Hibernate虽然全自动化,但是SQL语句可能不是最优的


。避免了几乎所有的JDBC代码中手动设置参数以及获取结果集


。支持XML开发,也支持注解式开发。


  • 为了保证sql语句的灵活,所以mybatis大部分是采用XML方式开发。


。将接口和Java的POJOs(Plain Ordinary Java Object,简单普通的Java对象)映射成数据库中的记录


。体积小好学:两个jar包,两个XML 配置文件


。完全做到sq|解耦合


。提供了基本映射标签


。提供了高级映射标签


。提供了XML标签,支持动态SQL的编写


二、开发第一个mybatis程序


开发我的第一个MyBatis程序


1.resources目录:


  • 放在这个目录当中的,一般都是资源文件,配置文件。


  • 直接放到resources目录下的资源,等同于放到了类的根路径下。


2.开发步骤


  • 第一步:打包方式jar


  • 第二步:引入依赖


https://www.mvnrepository.com/ 寻找相关依赖


。mybatis依赖


<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.10</version>
</dependency>


。mysql驱动依赖


<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>


  • 第三步:编写mybatis核心配置文件:mybatis-config.xml


。这个文件名不是必须叫做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"/>
    <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>
<!--        执行XxxMapper.xml文件的路径-->
<!--        resource属性会自动从根目录下开始查找-->
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>


。报错添加jdbc.properties至rescources


并在configuration下面


  • 第四步:编写XxxxMapper.xml文件


。在这个配置文件当中编写SQL语句。


。这个文件名也不是固定的,放的位置也不是固定,我们这里给它起个名字,叫做:CarMapper.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="ddda">
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values(null,'1003','丰田',30.0,'2003-9-10','燃油车')
    </insert>
</mapper>


  • 第五步:在mybatis-config.xml文件中指定XxxxMapper.xml文件的路径:


<mapper resource="CarMapper.xml"/>


注意:resource属性会自动从类的根路径下开始查找资源。


  • 第六步:编写MyBatis程序。(使用mybatis的类库,编写mybatis程序,连接数据库,做增删改查就行了。)


。在MyBatis当中,负责执行SQL语句的那个对象叫做什么呢?


  • SqlSession


SqlSession是专门用来执行SQL语句的,是一个Java程序和数据库之间的一次会话。


  • 要想获取SqlSession对象,需要先获取SqlSessionFactory对象,通过SqlSessionFactory工厂来生产SqlSession对象。


  • 怎么获取SqlSessionFactory对象呢?


  • 需要首先获取SqlSessionFactoryBuilder对象。


通过SqlSessionFactoryBuilder对象的build方法,来获取一个SqlSessionFactory对象。


。mybatis的核心对象包括:


  • SqlSessionFactoryBuilder
  • SqlSessionFactory
  • SqlSession


SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession


package com.st.mybatis.test;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
 * @author: TIKI
 * @Project: mybatis -MyBatisIntroductionTest
 * @Pcakage: com.st.mybatis.test.MyBatisIntroductionTest
 * @Date: 2022年10月28日 19:26
 * @Description:
 */
public class MyBatisIntroductionTest {
    public static void main(String[] args) throws IOException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 输入流指向核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");// Resources.getResourceAsStream默认从类的根路径下开始查找资源
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        int count = sqlSession.insert("insertCar");// 执行sql语句
        System.out.println("插入了几条记录:" + count);
        sqlSession.commit();// 手动提交 底层调用了conn.commit()
    }
}


3.从 XML 中构建 SqlSessionFactory


  • 通过官方的这句话,你能想到什么呢?


。第一:在MyBatis中一定是有一个很重要的对象,这个对象是:对象。

。第二:SqlSessionFactory对象的创建需要XML。


  • XML是什么?


它一定是一个配置文件。


4.mybatis中有两个主要的配置文件:


  • mybatis-config.xml,这是核心配置文件,主要配置连接数据库的信息等。(一个)


  • XxxxMapper.xml,这个文件是专门用来编写SQL语句的配置文件。(一个表一个)


。t_user表,一般会对应一个UserMapper.xml

。t_student表,一般会对应一个StudentMapper.xml


5.关于第一个程序的小细节


  • mybatis中sql语句的结尾";"可以省略。


  • Resources.getResourceAsStream


。小技巧:以后凡是遇到resource这个单词,大部分情况下,这种加载资源的方式就是从类的根路径下开始加载。(开始查找)


。优点:采用这种方式,从类路径当中加载资源,项目的移植性很强。项目从windows移植到linux,代码不需要修改,因为这个资源文件一直都在类路径当中。


  • 输入流


。InputStream is = new FileInputStream(“d:\mybatis-config.xml”);


  • 采用这种方式也可以。


  • 缺点:可移植性太差,程序不够健壮。可能会移植到其他的操作系统当中。导致以上路径无效,还需要修改java代码中的路径。这样违背了OCP原则。


。InputStream is = **ClassLoader.getSystemClassLoader().**getResourceAsStream(“mybatis-config.xml”);


  • ClassLoader.getSystemClassLoader() 获取系统的类加载器。

系统类加载器有一个方法叫做:getResourceAsStream,它就是从类路径当中加载资源的。


  • 通过源代码分析发现:

InputStream is = Resources.getResourceAsStream(“mybatis-config.xml”);

底层的源代码其实就是:

InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(“mybatis-config.xml”);


  • mybatis核心配置文件的名字,不一定是:mybatis-config.xml。可以是其它名字。

mybatis核心配置文件存放的路径,也不一定是在类的根路径下。可以放到其它位置。但为了项目的移植性,健壮性,最好将这个配置文件放到类路径下面。


  • CarMapper.xml文件的名字是固定的吗?CarMapper.xml文件的路径是固定的吗?

都不是固定的。


resource属性:这种方式是从类路径当中加载资源。

url属性:这种方式是从绝对路径当中加载资源。


6.关于mybatis的事务管理机制。(深度剖析)


  • 在mybatis-config.xml文件中,可以通过以下的配置进行mybatis的事务管理


<transactionManager type="JDBC"/>


  • type属性的值包括两个,不区分大小写


。JDBC(jdbc)


JDBC事务管理器


。MANAGED(managed)


MANAGED事务管理器:


  • mybatis 提供了 Transaction接口,该接口有两个实现类


。JdbcTransaction

。ManagedTransaction


  • JDBC事务管理器:


。mybatis框架自己管理事务,自己采用原生的JDBC代码去管理事务:


  • conn.setAutoCommit(false);


开启事务。


  • …业务处理…


  • conn.commit();


手动 提交事务


。使用JDBC事务管理器的话,底层创建的事务管理器对象:JdbcTransaction对象。


。如果你编写的代码是下面的代码:


SqlSession sqlSession = sqlSessionFactory.openSession(true);// 表示没有开启事务。 因为这种方式压根不会执行:conn.setAutoCommit(false);
SqlSession sqlSession = sqlSessionFactory.openSession();// 底层调用conn.setAutoCommit(false);


MANAGED事务管理器:


。mybatis不再负责事务的管理了。事务管理交给其它容器来负责。例如:spring。


。对于我们当前的单纯的只有mybatis的情况下,如果配置为:MANAGED

那么事务这块是没人管的。没有人管理事务表示事务压根没有开启。


没有人管理事务就是没有事务。


  • JDBC中的事务:

如果你没有在JDBC代码中执行:conn.setAutoCommit(false);的话,默认的autoCommit是true。


  • 在JDBC事务中,没有执行conn.setAutoCommit(false);那么autoCommit就是true。

如果autoCommit是true,就表示没有开启事务。只要执行任意一条DML语句就提交一次。


  • 重点:

只要你的autoCommit是true(自动提交),就表示没有开启事务。

只有你的autoCommit是false的时候,就表示开启了事务。


7.完整版MyBatis程序


package com.st.mybatis.test;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
/**
 * @author: TIKI
 * @Project: mybatis -MyBatisCompleteTest
 * @Pcakage: com.st.mybatis.test.MyBatisCompleteTest
 * @Date: 2022年10月29日 13:21
 * @Description: 完整版的MyBatis程序
 */
public class MyBatisCompleteTest {
    public static void main(String[] args) {
        SqlSession sqlSession = null;
        try {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession = sqlSessionFactory.openSession();
            // 执行SQL语句,处理相关业务
            int count = sqlSession.insert("insertCar");
            System.out.println(count);
            // 执行到治理,没有发生任何异常,提交事务
            sqlSession.commit();
        } catch (Exception e) {
            // 回滚事务
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        }finally {
            // 关闭会话(释放资源)
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}


8.junit单元测试


  • 单元测试(测试方法):用的是junit, junit是一个专门测试的框架(工具)。


。junit测试的内容: 测试的是类中的方法, 每一个方法都是独立测试的。

。方法是测试的基本单位(单元)。


  • 单元测试中有两个重要的概念


。实际值:被测试的业务方法的真正执行结果

。期望值:执行这个业务方法之后,你期望的执行结果


  • 在pom中加入相应的依赖


<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>


  • maven项目中的src/test/java目录下,创建测试程序。


。推荐的创建类和方法的提示:


1.测试类的名称 是Test + 你要测试的类名


例如你要测试HelloMaven , 创建测试类 TestHelloMaven


2.测试的方法名称 是:Test + 方法名称


  • 方法是public的,必须的
  • 方法没有返回值, 必须的
  • 方法名称是自定义的,推荐是Test + 方法名称
  • 在方法的上面加入 @Test


package com.st.junit.service;
/**
 * @author: TIKI
 * @Project: mybatis -MathService
 * @Pcakage: com.st.junit.service.MathService
 * @Date: 2022年10月29日 13:56
 * @Description:
 */
public class MathService {
    public int sum(int a, int b){
        return a+b;
    }
    public int sub(int a, int b){
        return a-b;
    }
}


public class MathServiceTest {
    @Test
    public void testSum(){
        MathService mathService = new MathService();
        int actual = mathService.sum(1,2);
        int expected = 3;
        // 加断言进行测试
        Assert.assertEquals(expected,actual);
    }
    @Test
    public void testSub(){
        MathService mathService = new MathService();
        int actual = mathService.sub(1,2);
        int expected = -1;
        // 加断言进行测试
        Assert.assertEquals(expected,actual);
    }
}


9.mybatis集成日志组件[调试起来更加方便]


  • mybatis常见的集成的日志组件有哪些呢?


SLF4J(沙拉风):沙拉风是一个日志标准


。logback,它实现了沙拉风规范。


。LOG4J


。LOG4J2


。STDOUT_LOGGING


注意:log4j log4j2 logback都是同一个作者开发的。


  • STDOUT_LOGGING是标准日志,mybatis已经实现了这种标准日志。mybatis框架本身已经实现了这种标准。只要开启即可。


。怎么开启呢?在mybatis-config.xml文件中在configuration使用settings标签进行配置开启。


<settings>
      <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>


。这个标签在编写的时候要注意,它应该出现在environments标签之前。


  • 注意顺序。当然,不需要记忆这个顺序。


因为有dtd文件进行约束呢。我们只要参考dtd约束即可。


。这种可以看到一些信息,比如:连接对象什么时候创建,什么时候关闭,sql语句是怎样的。


但是没有详细的日期,线程名字,等。如果你想使用更加丰富的配置,可以集成第三方的log组件。


  • 集成logback日志框架。


logback日志框架实现了SLF4J标准。(沙拉风:日志门面。日志标准。)


1.第一步:引入logback的依赖。


    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.11</version>
    </dependency>


2.第二步:引入logback所必须的xml配置文件。


。这个配置文件的名字必须叫做:logback.xml或者logback-test.xml,不能是其它的名字。

。这个配置文件必须放到类的根路径下。

。主要配置日志输出相关的级别以及日志具体的格式。


<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>[%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!--mybatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>
    <!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>


三、使用mybatis完成CRUD


1.什么是CRUD


  • C: Create增
  • R: Retrieve查(检索)
  • U: Update改
  • D: Delete删


2.insert


  • 最原始的insert代码


<insert id="insertCar">
    insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
    values(null,'1003','丰田霸道',30.0,'2000-10-11','燃油车');
</insert>


。这样写的问题是?


  • 值是写死到配置文件中的。


  • 这个在实际开发中不存在,在实际开发中一定是前端的form表单提交过来数据,然后将值传给sql语句。


  • JDBC的代码是怎么写的?


String sql = "insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,?,?,?,?,?)";
ps.setString(1, xxx);
ps.setString(2, yyy);
....


  • 在JDBC当中占位符采用的是?,在mybatis当中是什么呢?


。和?等效的写法是:#{}

。在mybatis当中不能使用?占位符,必须使用 #{} 来代替JDBC当中的 ?

。#{} 和 JDBC当中的 ? 是等效的。


  • java程序中使用Map可以给SQL语句的占位符传值:


Map<String, Object> map = new HashMap<>();
map.put("k1", "1111");
map.put("k2", "比亚迪汉");
map.put("k3", 10.0);
map.put("k4", "2020-11-11");
map.put("k5", "电车");


  • MyBatis的insert代码


insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});


。注意:#{}这里写什么?写map集合的key,如果key不存在,获取的是null


。一般map集合的key起名的时候要见名知意。


。测试代码


public class CarMapperTest {
    @Test
    public void testInsertCar(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 前段传过来的数据
        Map<String,Object> map = new HashMap<>();
        map.put("carNum", "1111");
        map.put("brand", "比亚迪汉2");
        map.put("guidePrice", 10.0);
        map.put("produceTime", "2020-11-11");
        map.put("carType", "电车");
        sqlSession.insert("insertCar",map);
        sqlSession.commit();
        sqlSession.close();
    }
}


  • java程序中使用POJO类给SQL语句的占位符传值:

Car car = new Car(null, “3333”, “比亚迪秦”, 30.0, “2020-11-11”, “新能源”);


。注意:占位符#{},大括号里面写:pojo类的属性名


insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})


。如果把SQL语句写成这个德行:


insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values(null,#{xyz},#{brand},#{guidePrice},#{produceTime},#{carType})


  • 出现了什么问题呢?


There is no getter for property named ‘xyz’ in ‘class com.powernode.mybatis.pojo.Car’


mybatis去找:Car类中的getXyz()方法去了。没找到。报错了。


  • 怎么解决的?


可以在Car类中提供一个getXyz()方法。这样问题就解决了。


  • 通过这个测试,得出一个结论:如果使用POJO对象传递值的话,#{}这个大括号中到底写什么?


写的是get方法的方法名去掉get,然后将剩下的单词首字母小写,然后放进去。


  • 例如:getUsername() --> #{username}


  • 例如:getEmail() --> #{email}


  • 也就是说mybatis在底层给?传值的时候,先要获取值,怎么获取的?


调用了pojo对象的get方法。例如:car.getCarNum(),car.getCarType(),car.getBrand()


。测试代码


  @Test
    public void testInsertCarByPojo(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 封装数据
        Car car= new Car(null,"333","比亚迪秦",30.0,"2020-10-20","新能源");
        sqlSession.insert("insertCar",car);
        sqlSession.commit();
        sqlSession.close();
    }


3.delete


  • 需求:根据id删除数据

将id=59的数据删除。


  • 实现:


    int count = sqlSession.delete("deleteById", 59);


    <delete id="deleteById">
        delete from t_car where id = #{fdsfd}
    </delete>


注意:如果占位符只有一个,那么#{}的大括号里可以随意。但是最好见名知意。


4.update


  • 需求:根据id修改某条记录。


  • 实现:


   <update id="updateById">
        update t_car set
             car_num=#{carNum},
             brand=#{brand},
             guide_price=#{guidePrice},
             produce_time=#{produceTime},
             car_type=#{carType}
        where
            id = #{id}
    </update>


Car car = new Car(4L, "9999", "凯美瑞", 30.3, "1999-11-10", "燃油车");
int count = sqlSession.update("updateById", car);


5.select(查一个)


根据主键查询的话,返回的结果一定是一个。


  • 需求:根据id查询。


  • 实现


    <select id="selectById" resultType="com.st.mybatis.pojo.Car">
        select * from t_car where id = #{id}
    </select>


Object car = sqlSession.selectOne("selectById", 1);


  • 需要特别注意的是:


。select标签中resultType属性,这个属性用来告诉mybatis,查询结果集封装成什么类型的java对象。


。resultType通常写的是:全限定类名。


  • 输出结果有点不对劲:


Car{id=1, carNum=‘null’, brand=‘宝马520’, guidePrice=null, produceTime=‘null’, carType=‘null’}


。id和brand属性有值,其他属性为null。


  • carNum以及其他的这几个属性没有赋上值的原因是什么?


    select * from t_car where id = 1
    执行结果:
    +----+---------+-----------+-------------+--------------+----------+
    | id | car_num | brand     | guide_price | produce_time | car_type |
    +----+---------+-----------+-------------+--------------+----------+
    |  1 | 1001    | 宝马520Li |       10.00 | 2020-10-11   | 燃油车   |
    +----+---------+-----------+-------------+--------------+----------+


。car_num、guide_price、produce_time、car_type这是查询结果的列名。

这些列名和Car类中的属性名对不上。

Car类的属性名:carNum、guidePrice、produceTime、carType


。那这个问题怎么解决呢?


select语句查询的时候,查询结果集的列名使用as关键字起别名的。


<select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
    select
        id,car_num as carNum,brand,guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
    from
        t_car
    where
        id = #{id}
</select>
起别名之后:
+----+--------+-----------+------------+-------------+---------+
| id | carNum | brand     | guidePrice | produceTime | carType |
+----+--------+-----------+------------+-------------+---------+
|  1 | 1001   | 宝马520Li |      10.00 | 2020-10-11  | 燃油车  |
+----+--------+-----------+------------+-------------+---------+


6.select(查所有的)


  • 实现


<select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
    select
        id,car_num as carNum,brand,guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
    from
        t_car
</select>

List<Object> cars = sqlSession.selectList("selectAll");


  • 注意:resultType还是指定要封装的结果集的类型。不是指定List类型,是指定List集合中元素的类型。


selectList方法:mybatis通过这个方法就可以得知你需要一个List集合。它会自动给你返回一个List集合。


7.namespace


  • 在sql mapper.xml文件当中有一个namespace,这个属性是用来指定命名空间的。用来防止id重复。


  • 怎么用?

在xml文件中:


     <mapper namespace="car">
         <select id="selectAll" resultType="com.powernode.mybatis.pojo.Car">
             select
                 id,car_num as carNum,brand,guide_price as guidePrice,
                 produce_time as produceTime,
                 car_type
             from
                 t_car
         </select>
      </mapper>


在java程序中的写法:


     List<Object> cars = sqlSession.selectList("car.selectAll");


  • 实际上,本质上,mybatis中的sqlId的完整写法: namespace.id


四、MyBatis核心配置文件


第一行表明xml文件根标签的内容,一个xml只有一个根,以及采用的dtd约束


configuration配置


properites 属性


  • java.util.Properties类。是一个Map集合。key和value都是String类型


  • 在properties标签中可以配置很多属性


    <!--<properties>-->
        <!--这是其中的一个属性-->
        <!--<property name="属性名" value="属性值"/>-->
        <property name="jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/st"/>
        <property name="jdbc.username" value="root"/>
        <property name="jdbc.password" value="root"/>
    <!--</properties>-->


  • 使用


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


  • 使用属性配置文件


<!--resource,一定是从类路径下开始查找资源-->
<!--<properties resource="jdbc.properties" />-->
<!--从绝对路径当中加载资源。绝对路径怎么写?file:///路径-->
<properties url="file:///d:/jdbc.properties" />


jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.jdbc.jdbc.url=jdbc:mysql://localhost:3306/st
jdbc.jdbc.username=root
jdbc.password=123


environments 环境配置


  • 一个configuration中可以包含多个环境


  • 一个环境对应一个数据库


  • 一个环境对应一个SqlSessionFactory对象


  • 如何配置环境?


<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>


。default


默认使用的环境 ID(比如:default=“development”)。


。id


每个 environment 元素定义的环境 ID(比如:id=“development”)。


。transactionManager


事务管理器的配置(比如:type=“JDBC”)。


。dataSource


数据源的配置,使用哪个数据库连接池(比如:type=“POOLED”)


  • 根据环境id创建SqlSessionFactory对象


SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"),"tiki");
SqlSession sqlSession = sqlSessionFactory.openSession();
// sql语句
sqlSession.commit();
sqlSession.close();


datasource


  • 为程序提供Connection对象 [但凡是给程序提供Connection对象的,都叫做数据源]


  • 数据源实际上是一套规范,JDK中有这套规范(接口):javax.sql.DataSource


  • 数据库连接池实现了该接口,所以就是数据源


  • 常见的数据源组件【数据库连接池】


1.阿里巴巴的德鲁伊连接处:druid

2.c3p0

3.dbcp


  • type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象


type=“[UNPOOLED|POOLED|JNDI]”


1.UNPOOLED:不使用数据库连接池技术。每一次请求过来之后,都是创建新的Connection对象。


2.POOLED:使用mybatis自己实现的数据库连接池。


3.JNDI:集成其它第三方的数据库连接池。

。JNDI是一套规范。

。谁实现了这套规范呢?大部分的web容器都实现了JNDI规范:

例如:Tomcat、Jetty、WebLogic、WebSphere,这些服务器(容器)都实现了JNDI规范。

。JNDI是:java命名目录接口。Tomcat服务器实现了这个规范。


  • 连接池的优点


。效率高

。连接对象的创建数量可控


24d8f77b7aa50478f9aa6b2a6a369ea7.png


  • UNPOOLED参数配置


。driver – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。

。url – 这是数据库的 JDBC URL 地址。

。username – 登录数据库的用户名。

。password – 登录数据库的密码。

。defaultTransactionIsolationLevel – 默认的连接事务隔离级别。

。defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 java.sql.Connection#setNetworkTimeout() 的 API 文档以获取更多信息


  • 除了以上参数外,POOLED池中常见参数配置有:


。poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限。


最多有多少个连接可以活动。默认值10


。poolTimeToWait:如果获取连接花费了相当长的时间,连接池会每隔2秒打印日志,并且尝试获取连接对象


。poolMaximumCheckoutTime:在被强制返回之前,池中连接被检出(checked out)时间


默认值:20000 毫秒(即 20 秒)


。poolMaximumIdleConnections:最多的空闲数量


            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
                <!--提醒:正常使用连接池的话,池中有很多参数是需要设置的。设置好参数,可以让连接池发挥的更好。事半功倍的效果。-->
                <!--具体连接池当中的参数如何配置呢?需要反复的根据当前业务情况进行测试。-->
                <!--poolMaximumActiveConnections:连接池当中最多的正在使用的连接对象的数量上限。最多有多少个连接可以活动。默认值10-->
                <property name="poolMaximumActiveConnections" value="10"/>
                <!--每隔2秒打印日志,并且尝试获取连接对象-->
                <property name="poolTimeToWait" value="2000"/>
                <!--强行让某个连接空闲,超时时间的设置-->
                <property name="poolMaximumCheckoutTime" value="10000"/>
                <!--最多的空闲数量-->
                <property name="poolMaximumIdleConnections" value="5"/>
            </dataSource>


mapper


指定SQL映射文件的路径


    <mappers>
<!--        执行XxxMapper.xml文件的路径-->
<!--        resource属性会自动从根目录下开始查找-->
        <mapper resource="CarMapper.xml"/>
    </mappers>


五、手写MyBatis框架【无基础先跳过】


5.1 dom4j解析XML文件


第一步:编写pom.xml 引入dom4j依赖


<?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.st</groupId>
    <artifactId>parse-xml-by-dom4j</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>
    <!--依赖-->
    <dependencies>
        <!--dom4j的依赖-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--jaxen依赖-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
</project>


第二步:编写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>
    <environments default="mybatisDB">
        <environment id="stDB">
            <transactionManager type="MANAGED"/>
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/st"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="mybatisDB">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>


第三步:使用dom4j解析核心配置文件文件


@Test
    public void testParseMyBatisConfigXML() throws Exception{
        // 创建SAXReader对象
        SAXReader reader = new SAXReader();
        // 获取输入流
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
        // 读XML文件,返回document对象。document对象是文档对象,代表了整个XML文件。
        Document document = reader.read(is);
        // 获取文档当中的根标签
        //Element rootElt = document.getRootElement();
        //String rootEltName = rootElt.getName();
        //System.out.println("根节点的名字:" + rootEltName);
        //获取default默认的环境id
        // xpath是做标签路径匹配的。能够让我们快速定位XML文件中的元素。
        // 以下的xpath代表了:从根下开始找configuration标签,然后找configuration标签下的子标签environments
        String xpath = "/configuration/environments";
        Element environments = (Element) document.selectSingleNode(xpath); // Element是Node类的子类,方法更多,使用更便捷。
        // 获取属性的值
        String defaultEnvironmentId = environments.attributeValue("default");
        //System.out.println("默认环境的id:" + defaultEnvironmentId);
        // 获取具体的环境environment
        xpath = "/configuration/environments/environment[@id='"+defaultEnvironmentId+"']";
        //System.out.println(xpath);
        Element environment = (Element) document.selectSingleNode(xpath);
        // 获取environment节点下的transactionManager节点(Element的element()方法用来获取孩子节点)
        Element transactionManager = environment.element("transactionManager");
        String transactionType = transactionManager.attributeValue("type");
        System.out.println("事务管理器的类型:" + transactionType);
        // 获取dataSource节点
        Element dataSource = environment.element("dataSource");
        String dataSourceType = dataSource.attributeValue("type");
        System.out.println("数据源的类型:" + dataSourceType);
        // 获取dataSource节点下的所有子节点
        List<Element> propertyElts = dataSource.elements();
        // 遍历
        propertyElts.forEach(propertyElt -> {
            String name = propertyElt.attributeValue("name");
            String value = propertyElt.attributeValue("value");
            System.out.println(name + "=" + value);
        });
        // 获取所有的mapper标签
        // 不想从根下开始获取,你想从任意位置开始,获取所有的某个标签,xpath该这样写
        xpath = "//mapper";
        List<Node> mappers = document.selectNodes(xpath);
        // 遍历
        mappers.forEach(mapper -> {
            Element mapperElt = (Element) mapper;
            String resource = mapperElt.attributeValue("resource");
            System.out.println(resource);
        });
    }


第四步:编写配置文件Carmapper.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="car">
    <insert id="insertCar">
        insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>
    <select id="selectById" resultType="com.powernode.mybatis.pojo.Car">
        select
            id,car_num as carNum,brand,guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from
            t_car
        where
            id = #{id}
    </select>
</mapper>


第五步:解析CarMapper.xml


    @Test
    public void testParseSqlMapperXML() throws Exception{
        SAXReader reader = new SAXReader();
        InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");
        Document document = reader.read(is);
        // 获取namespace
        String xpath = "/mapper";
        Element mapper = (Element) document.selectSingleNode(xpath);
        String namespace = mapper.attributeValue("namespace");
        System.out.println(namespace);
        // 获取mapper节点下所有的子节点
        List<Element> elements = mapper.elements();
        // 遍历
        elements.forEach(element -> {
            // 获取sqlId
            String id = element.attributeValue("id");
            System.out.println(id);
            // 获取resultType
            String resultType = element.attributeValue("resultType"); // 没有这个属性的话,会自动返回"null"
            System.out.println(resultType);
            // 获取标签中的sql语句(表示获取标签中的文本内容,而且去除前后空白)
            String sql = element.getTextTrim();
            System.out.println(sql);
            // insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
            // insert into t_car values(null,?,?,?,?,?)
            // mybaits封装了jdbc。早晚要执行带有?的sql语句。
            // 转换
            String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");
            System.out.println(newSql);
        });
    }


5.2 GodBatis


手写框架之前,如果没有思路,可以先参考一下mybatis的客户端程序,通过客户端程序来逆推需要的类,参考代码:


package com.st.mybatis.test;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
/**
 * @author: TIKI
 * @Project: mybatis -MyBatisCompleteTest
 * @Pcakage: com.st.mybatis.test.MyBatisCompleteTest
 * @Date: 2022年10月29日 13:21
 * @Description: 完整版的MyBatis程序
 */
public class MyBatisCompleteTest {
    public static void main(String[] args) {
        SqlSession sqlSession = null;
        try {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession = sqlSessionFactory.openSession();
            // 执行SQL语句,处理相关业务
            int count = sqlSession.insert("insertCar");
            System.out.println(count);
            // 执行到治理,没有发生任何异常,提交事务
            sqlSession.commit();
        } catch (Exception e) {
            // 回滚事务
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        }finally {
            // 关闭会话(释放资源)
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}


第一步:IDEA中创建模块


  • 模块:godbatis(创建普通的Java Maven模块,打包方式jar),引入相关依赖


<?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>org.god.ibatis</groupId>
    <artifactId>godbatis</artifactId>
    <version>1.0</version>
    <packaging>jar</packaging>
    <!--依赖-->
    <dependencies>
        <!--dom4j依赖-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--jaxen依赖-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--mysql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
</project>


第二步:资源工具类,方便获取指向配置文件的输入流


  • 这个工具类专门完成“类路径”中资源的加载。


  • 工具类的构造方法都是建议私有化的。


。因为工具类中的方法都是静态的,不需要创建对象就能调用。

。为了避免new对象,所有构造方法私有化。


  • 代码


package org.god.ibatis.utils;
import java.io.InputStream;
/**
 * godbatis框架提供的一个工具类。
 * 这个工具类专门完成“类路径”中资源的加载。
 * @author 动力节点
 * @since 1.0
 * @version 1.0
 */
public class Resources {
    /**
     * 工具类的构造方法都是建议私有化的。
     * 因为工具类中的方法都是静态的,不需要创建对象就能调用。
     * 为了避免new对象,所有构造方法私有化。
     * 这只是一种编程习惯。
     */
    private Resources(){}
    /**
     * 从类路径当中加载资源。
     * @param resource 放在类路径当中的资源文件。
     * @return 指向资源文件的一个输入流。
     */
    public static InputStream getResourceAsStream(String resource){
        return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
    }
}


第三步:定义SqISessionFactoryBuilder类


  • SqISessionFactoryBuilder:SqlSessionFactory构建器对象。


通过SqlSessionFactoryBuilder的build方法来解析godbatis-config.xml文件,然后创建SqlSessionFactory对象。


第四步:分析SqlSessionFactory类中有哪些属性


  • SqISessionFactoryBuilder.build方法返回一个SqlSessionFactory类对象,那么这个对象应该具有哪些属性呢?


根据核心配置文件,SqlSessionFactory类中至少有以下属性


  • 事务管理器属性:可以灵活切换->接口


  • 数据源属性:分析可得SqlSessionFactory类中可以不设置数据源


事务管理器对象中需要数据源对象获取连接对象Connection,因此可以通过事务管理器对象获取数据源对象


  • 一个大的Map集合:存储所有mapper映射文件的sql语句,key为sqlId,value为封装好的SQL标签信息对象MappedStatement


bbe80ed2a6f9d4f65378e9618a59417d.png


Transaction接口


  • Transaction事务管理器接口:提供管理事务方法


。所有的事务管理器都应该遵循该规范。

。JDBC事务管理器,MANAGED事务管理器都应该实现这个接口


  • 代码


package org.god.ibatis.core;
import java.sql.Connection;
/**
 * 事务管理器接口。
 * 所有的事务管理器都应该遵循该规范。
 * JDBC事务管理器,MANAGED事务管理器都应该实现这个接口。
 * Transaction事务管理器:提供管理事务方法。
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public interface Transaction {
    /**
     * 提交事务
     */
    void commit();
    /**
     * 回滚事务
     */
    void rollback();
    /**
     * 关闭事务
     */
    void close();
    /**
     * 真正的开启数据库连接。
     */
    void openConnection();
    /**
     * 获取数据库连接对象的。
     */
    Connection getConnection();
}


MappedStatement POJO类


  • 根据sql标签,定义SQL标签信息对象MappedStatement[简单的]


。一个MappedStatement对象对应一个SQL标签

。一个SQL标签中的所有信息封装到MappedStatement对象当中

。面向对象编程思想


  • 属性


。private String sql;


sql语句


。private String resultType;


要封装的结果集类型。有的时候resultType是null。


  • 比如:insert delete update语句的时候resultType是null。
  • 只有当sql语句是select语句的时候resultType才有值。


package org.god.ibatis.core;
/**
 * 普通的java类。POJO,封装了一个SQL标签。
 * 一个MappedStatement对象对应一个SQL标签。
 * 一个SQL标签中的所有信息封装到MappedStatement对象当中。
 * 面向对象编程思想。
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class MappedStatement {
    /**
     * sql语句
     */
    private String sql;
    /**
     * 要封装的结果集类型。有的时候resultType是null。
     * 比如:insert delete update语句的时候resultType是null。
     * 只有当sql语句是select语句的时候resultType才有值。
     */
    private String resultType;
    @Override
    public String toString() {
        return "MappedStatement{" +
                "sql='" + sql + '\'' +
                ", resultType='" + resultType + '\'' +
                '}';
    }
    public String getSql() {
        return sql;
    }
    public void setSql(String sql) {
        this.sql = sql;
    }
    public String getResultType() {
        return resultType;
    }
    public void setResultType(String resultType) {
        this.resultType = resultType;
    }
    public MappedStatement(String sql, String resultType) {
        this.sql = sql;
        this.resultType = resultType;
    }
    public MappedStatement() {
    }
}


第五步:定义JdbcTransaction


GodBatis只对JdbcTransaction进行实现


  • 思路


。控制事务的时候需要通过连接对象Connecton进行事务的提交、回滚以及关闭


。那么Connection对象从哪里来?—>通过属性数据获得Connection对象


因此SqlSessionFactory类中可以不设置数据源属性:通过事务管理器对象获取数据源对象


。commit、rollback、close方法中需使用一个Connection对象,因此需要添加属性connection,通过openConnection方法对空的connection进行赋值,真正开启数据库连接


  • 属性


。private DataSource dataSource;

。private boolean autoCommit;

。private Connection connection;


  • 方法


。public void commit()

。public void rollback()

。public void close()

。public void openConnection()

。public Connection getConnection()


  • 通过数据源对事务管理器进行完善代码


package org.god.ibatis.core;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
 * JDBC事务管理器(godbatis框架目前只对JdbcTransaction进行实现。)
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class JdbcTransaction implements Transaction{
    /**
     * 数据源属性
     * 经典的设计:面向接口编程。
     */
    private DataSource dataSource;
    /**
     * 自动提交标志
     * true表示自动提交
     * false表示不采用自动提交
     */
    private boolean autoCommit;
    /**
     * 连接对象
     */
    private Connection connection;
    @Override
    public Connection getConnection() {
        return connection;
    }
    /**
     * 创建事务管理器对象
     * @param dataSource
     * @param autoCommit
     */
    public JdbcTransaction(DataSource dataSource, boolean autoCommit) {
        this.dataSource = dataSource;
        this.autoCommit = autoCommit;
    }
    @Override
    public void commit() {
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void rollback() {
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void close() {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void openConnection(){
        if (connection == null) {
            try {
                connection = dataSource.getConnection();
                // 开启事务
                connection.setAutoCommit(autoCommit);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}


第六步︰事务管理器中需要数据源,定义UnpooledDataSource


  • 数据源用于获取connection对象


  • 数据源种类有:POOLED UNPOOLED JNDI


。因此也需要设计一个接口

。好消息:所有数据源都要实现JDK的规范:javax.sql.DataSource

。因此不需要自己设计接口


  • UnPooledDataSource实现类


。不使用连接池,每一次都新建Connection对象。

。属性


  • url
  • username
  • password


package org.god.ibatis.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
 * 数据源的实现类:UNPOOLED (重点实现这种方式。)
 * 不使用连接池,每一次都新建Connection对象。
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class UnPooledDataSource implements javax.sql.DataSource{
    private String url;
    private String username;
    private String password;
    /**
     * 创建一个数据源对象。
     * @param driver
     * @param url
     * @param username
     * @param password
     */
    public UnPooledDataSource(String driver, String url, String username, String password) {
        try {
            // 直接注册驱动
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        this.url = url;
        this.username = username;
        this.password = password;
    }
    @Override
    public Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(url, username, password);
        return connection;
    }
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }
    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }
    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {
    }
    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
    }
    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }
    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }
    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}


第七步:SqISessionFactoryBuilder获取数据源对象和事务管理器对象


  • 思路


。使用dom4j解析核心配置文件,获取事务管理器Transaction、数据源Datasource、SQL映射文件的标签元素,并根据其属性值获取相应的对象


/**
     * 获取事务管理器
     * @param transactionElt 事务管理器标签元素
     * @param dataSource 数据源对象
     * @return
     */
    private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
        Transaction transaction = null;
        String type = transactionElt.attributeValue("type").trim().toUpperCase();
        if (Const.JDBC_TRANSACTION.equals(type)) {
            transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。
        }
        if (Const.MANAGED_TRANSACTION.equals(type)) {
            transaction = new ManagedTransaction();
        }
        return transaction;
    }
    /**
     * 获取数据源对象
     * @param dataSourceElt 数据源标签元素
     * @return
     */
    private DataSource getDataSource(Element dataSourceElt) {
        Map<String,String> map = new HashMap<>();
        // 获取所有的property
        List<Element> propertyElts = dataSourceElt.elements("property");
        propertyElts.forEach(propertyElt -> {
            String name = propertyElt.attributeValue("name");
            String value = propertyElt.attributeValue("value");
            map.put(name, value);
        });
        DataSource dataSource = null;
        String type = dataSourceElt.attributeValue("type").trim().toUpperCase();// 去空格并转化为大写
        if (Const.UN_POOLED_DATASOURCE.equals(type)) {
            dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
        }
        if (Const.POOLED_DATASOURCE.equals(type)) {
            dataSource = new PooledDataSource();
        }
        if (Const.JNDI_DATASOURCE.equals(type)) {
            dataSource = new JNDIDataSource();
        }
        return dataSource;
    }
}


第八步:SqISessionFactoryBuilder获取存储SQL的Map集合


 /**
     * 解析所有的SqlMapper.xml文件,然后构建Map集合。
     * @param sqlMapperXMLPathList
     * @return
     */
    private Map<String, MappedStatement> getMappedStatements(List<String> sqlMapperXMLPathList) {
        Map<String, MappedStatement> mappedStatements = new HashMap<>();
        sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {
            try {
                SAXReader reader = new SAXReader();
                Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));
                Element mapper = (Element) document.selectSingleNode("mapper");// 根mapper
                String namespace = mapper.attributeValue("namespace");// 防止id重复
                List<Element> elements = mapper.elements();
                elements.forEach(element -> {
                    String id = element.attributeValue("id");
                    // 这里进行了namespace和id的拼接,生成最终的sqlId
                    String sqlId = namespace + "." + id;
                    String resultType = element.attributeValue("resultType");
                    String sql = element.getTextTrim();//除去前后空白
                    MappedStatement mappedStatement = new MappedStatement(sql, resultType);
                    mappedStatements.put(sqlId, mappedStatement);
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return mappedStatements;
    }


第九步:完善SqISessionFactoryBuilder中的buld方法


  • 技巧:将常量定义在Const类中


  • 主要方法


。public SqlSessionFactory build(InputStream in);


解析godbatis-config.xml文件,来构建SqlSessionFactory对象。


。private Map<String, MappedStatement> getMappedStatements(List sqlMapperXMLPathList);


解析所有的SqlMapper.xml文件,然后构建Map集合。


。private Transaction getTransaction(Element transactionElt, DataSource dataSource)


获取事务管理器


。private DataSource getDataSource(Element dataSourceElt)


获取数据源对象


  • 代码


package org.god.ibatis.core;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.god.ibatis.utils.Resources;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * SqlSessionFactory构建器对象。
 * 通过SqlSessionFactoryBuilder的build方法来解析
 * godbatis-config.xml文件,然后创建SqlSessionFactory对象。
 * @author 动力节点
 * @version 1.0
 * @since 1.0
 */
public class SqlSessionFactoryBuilder {
    /**
     * 无参数构造方法。
     */
    public SqlSessionFactoryBuilder(){}
    /**
     * 解析godbatis-config.xml文件,来构建SqlSessionFactory对象。
     * @param in 指向godbatis-config.xml文件的一个输入流。
     * @return SqlSessionFactory对象。
     */
    public SqlSessionFactory build(InputStream in){
        SqlSessionFactory factory = null;
        try {
            // 解析godbatis-config.xml文件
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            Element environments = (Element) document.selectSingleNode("/configuration/environments");
            String defaultId = environments.attributeValue("default");
            Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
            Element transactionElt = environment.element("transactionManager");
            Element dataSourceElt = environment.element("dataSource");
            List<String> sqlMapperXMLPathList = new ArrayList<>();
            List<Node> nodes = document.selectNodes("//mapper"); // //获取整个配置文件中所有的mapper标签
            nodes.forEach(node -> {
                Element mapper = (Element) node;
                String resource = mapper.attributeValue("resource");
                sqlMapperXMLPathList.add(resource);
            });
            // 获取数据源对象
            DataSource dataSource = getDataSource(dataSourceElt);
            // 获取事务管理器
            Transaction transaction = getTransaction(transactionElt,dataSource);
            // 获取mappedStatements
            Map<String, MappedStatement> mappedStatements = getMappedStatements(sqlMapperXMLPathList);
            // 解析完成之后,构建SqlSessionFactory对象。
            factory = new SqlSessionFactory(transaction, mappedStatements);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory;
    }
    /**
     * 解析所有的SqlMapper.xml文件,然后构建Map集合。
     * @param sqlMapperXMLPathList
     * @return
     */
    private Map<String, MappedStatement> getMappedStatements(List<String> sqlMapperXMLPathList) {
        Map<String, MappedStatement> mappedStatements = new HashMap<>();
        sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {
            try {
                SAXReader reader = new SAXReader();
                Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));
                Element mapper = (Element) document.selectSingleNode("mapper");// 根mapper
                String namespace = mapper.attributeValue("namespace");// 防止id重复
                List<Element> elements = mapper.elements();
                elements.forEach(element -> {
                    String id = element.attributeValue("id");
                    // 这里进行了namespace和id的拼接,生成最终的sqlId
                    String sqlId = namespace + "." + id;
                    String resultType = element.attributeValue("resultType");
                    String sql = element.getTextTrim();//除去前后空白
                    MappedStatement mappedStatement = new MappedStatement(sql, resultType);
                    mappedStatements.put(sqlId, mappedStatement);
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return mappedStatements;
    }
    /**
     * 获取事务管理器
     * @param transactionElt 事务管理器标签元素
     * @param dataSource 数据源对象
     * @return
     */
    private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
        Transaction transaction = null;
        String type = transactionElt.attributeValue("type").trim().toUpperCase();
        if (Const.JDBC_TRANSACTION.equals(type)) {
            transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。
        }
        if (Const.MANAGED_TRANSACTION.equals(type)) {
            transaction = new ManagedTransaction();
        }
        return transaction;
    }
    /**
     * 获取数据源对象
     * @param dataSourceElt 数据源标签元素
     * @return
     */
    private DataSource getDataSource(Element dataSourceElt) {
        Map<String,String> map = new HashMap<>();
        // 获取所有的property
        List<Element> propertyElts = dataSourceElt.elements("property");
        propertyElts.forEach(propertyElt -> {
            String name = propertyElt.attributeValue("name");
            String value = propertyElt.attributeValue("value");
            map.put(name, value);
        });
        DataSource dataSource = null;
        String type = dataSourceElt.attributeValue("type").trim().toUpperCase();// 去空格并转化为大写
        if (Const.UN_POOLED_DATASOURCE.equals(type)) {
            dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
        }
        if (Const.POOLED_DATASOURCE.equals(type)) {
            dataSource = new PooledDataSource();
        }
        if (Const.JNDI_DATASOURCE.equals(type)) {
            dataSource = new JNDIDataSource();
        }
        return dataSource;
    }
}


第十步:在SqISessionFactory中添加openSession方法


  • openSession方法:获取Sql会话对象


    /**
     * 获取Sql会话对象。
     * @return
     */
    public SqlSession openSession(){
        // 开启会话的前提是开启连接。(连接打开了)
        transaction.openConnection();
        // 创建SqlSession对象
        SqlSession sqlSession = new SqlSession(this);// 将SqlSessionFactory传入
        return sqlSession;
    }


  • SqlSession私有变量 构造方法


    private SqlSessionFactory factory;
    public SqlSession(SqlSessionFactory factory) {
        this.factory = factory;
    }


第十一步:编写SqlSession类中commit rollback close方法


  • 代码


    /**
     * 提交事务
     */
    public void commit(){
        factory.getTransaction().commit();
    }
    /**
     * 回滚事务
     */
    public void rollback(){
        factory.getTransaction().rollback();
    }
    /**
     * 关闭事务
     */
    public void close(){
        factory.getTransaction().close();
    }


第十二步:编写SqlSession类中的insert方法


  • 思路:将原sql转换为jdbc中的sql,并动态给占位符赋值


。属性名怎么获得?通过#的位置获得属性名⌈ \lceil⌈#后不能有空格⌋ \rfloor⌋

。属性值怎么获得?通过调用get属性名()方法获得属性值

。获得属性类型->set类型(index,属性值)


  • 代码


/**
     * 执行insert语句,向数据库表当中插入记录。
     * @param sqlId sql语句的id
     * @param pojo 插入的数据。
     * @return
     */
    public int insert(String sqlId, Object pojo){
        int count = 0;
        try {
            // JDBC代码,执行insert语句,完成插入操作。
            Connection connection = factory.getTransaction().getConnection();
            // insert into t_user values(#{id},#{name},#{age})
            String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();
            // insert into t_user(id,name,age) values(?,?,?)
            String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
            PreparedStatement ps = connection.prepareStatement(sql);
            // 给?占位符传值
            // 难度是什么:
            // 第一:你不知道有多少个?
            // 第二:你不知道该将pojo对象中的哪个属性赋值给哪个 ?
            // ps.String(第几个问号, 传什么值); // 这里都是setString,所以数据库表中的字段类型要求都是varchar才行。这是godbatis比较失败的地方。
            int fromIndex = 0;
            int index = 1;
            while(true){
                int jingIndex = godbatisSql.indexOf("#", fromIndex);
                if (jingIndex < 0) {
                    break;
                }
                int youKuoHaoIndex = godbatisSql.indexOf("}", fromIndex);
                String propertyName = godbatisSql.substring(jingIndex + 2, youKuoHaoIndex).trim();
                fromIndex = youKuoHaoIndex + 1;
                // 有属性名id,怎么获取id的属性值呢?调用getId()方法
                String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);
                Object propertyValue = getMethod.invoke(pojo);
                ps.setString(index, propertyValue.toString());
                index++;
            }
            // 执行SQL语句
            count = ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }


第十三步:编写SqISession类中的selectOne方法


  • 思路


。假设只有一个参数,那么直接传值即可

。怎么封装结果集?


  • 通过mappedStatement获取结果类型
  • 然后通过Class.forName获取Class
  • 调用无参数构造方法创建对象
  • 给obj的属性赋值:将查询结果的字段名作为属性名,拼接set方法进行赋值
  • rsmd.getColumnName(i + 1); 下标从1开始


  • 代码


/**
     * 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句。
     * @param sqlId
     * @param param
     * @return
     */
    public Object selectOne(String sqlId, Object param){
        Object obj = null;
        try {
            Connection connection = factory.getTransaction().getConnection();
            MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);
            // 这是那个DQL查询语句
            // select * from t_user where id = #{id}
            String godbatisSql = mappedStatement.getSql();
            String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
            PreparedStatement ps = connection.prepareStatement(sql);
            // 给占位符传值
            ps.setString(1, param.toString());
            // 查询返回结果集
            ResultSet rs = ps.executeQuery();
            // 要封装的结果类型。
            String resultType = mappedStatement.getResultType(); // org.god.ibatis.pojo.User
            // 从结果集中取数据,封装java对象
            if (rs.next()) {
                // 获取resultType的Class
                Class<?> resultTypeClass = Class.forName(resultType);
                // 调用无参数构造方法创建对象
                obj = resultTypeClass.newInstance(); // Object obj = new User();
                // 给User类的id,name,age属性赋值
                // 给obj对象的哪个属性赋哪个值。
                /*
                mysql> select * from t_user where id = '1111';
                +------+----------+------+
                | id   | name     | age  |
                +------+----------+------+
                | 1111 | zhangsan | 20   |
                +------+----------+------+
                解决问题的关键:将查询结果的列名作为属性名。
                列名是id,那么属性名就是:id
                列名是name,那么属性名就是:name
                 */
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                for (int i = 0; i < columnCount; i++) {
                    String propertyName = rsmd.getColumnName(i + 1);
                    // 拼接方法名
                    String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                    // 获取set方法
                    Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);
                    // 调用set方法给对象obj属性赋值
                    setMethod.invoke(obj, rs.getString(propertyName));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
    // 局部测试
    public static void main(String[] args) {
        String sql = "insert into t_user values(#{id},#{name},#{age})";
        int fromIndex = 0;
        int index = 1;
        while(true){
            int jingIndex = sql.indexOf("#", fromIndex);
            if (jingIndex < 0) {
                break;
            }
            System.out.println(index);
            index++;
            int youKuoHaoIndex = sql.indexOf("}", fromIndex);
            String propertyName = sql.substring(jingIndex + 2, youKuoHaoIndex).trim();
            System.out.println(propertyName);
            fromIndex = youKuoHaoIndex + 1;
        }
    }


5.3 GodBatis使用Maven打包


  • 双击install,在本地仓库中查看是否有jar包生成


776b02420f0c601eba79105c5daf3c1b.png


5.4 总结MyBatis框架的重要实现原理


  • 为什么insert语句中 #{} 里填写的必须是属性名?

。通过属性名给占位符赋值


  • 为什么select语句查询结果列名要属性名一致?

。将查询结果的字段名作为属性名,拼接set方法进行赋值


六、在WEB中应用MyBatis(使用MVC架构模式)


6.1 需求描述


完成银行账户转账的功能


6.2 数据库表的设计和准备数据


a541bbc54f9c978b10625f2694c7c824.png

3554fb9f161057336be20fb1dfcc686f.png


6.3 实现步骤


第一步 :环境搭建


1.创建maven web项目


c150d21523761999280054e96c47e4aa.png


2.默认创建的maven web应用没有java和sesources目录


。手动加

。修改maven-archetype-webapp-1.4.jar中的配置文件


3.配置tomcat


4.修改web.xml文件为高版本


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
  version="5.0"
  metadata-complete="true">
</web-app>


5.确定pom.xml文件中的打包方式是war包


8cbe8dd2af7d8830eea0dc4b62eaaf04.png


6.pom.xml 引入相关依赖


。编译器版本修改为17

。引入的依赖包括:mybatis,mysql,logback,servlet


<?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.st</groupId>
  <artifactId>mybatis004-web</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>
  <name>mybatis-004-web Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.10</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.30</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.11</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>mybatis004-web</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>


7.引入相关配置文件,放入resources目录


。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"/>
    <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>
<!--        执行XxxMapper.xml文件的路径-->
<!--        resource属性会自动从根目录下开始查找-->
        <mapper resource="AccountMapper.xml"/>
    </mappers>
</configuration>


。AccountMapper.xml


。logback.xml


<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
    <!-- 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>[%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>
    <!--mybatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>
    <!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>


。jdbc.properties


第二步:前段页面index.html


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>银行账户转账</title>
</head>
<body>
<form action="/bank/transfer" method="post">
    转出账户:<input type="text" name="fromActno"><br>
    转入账户:<input type="text" name="toActno"><br>
    转账金额:<input type="text" name="money"><br>
    <input type="submit" value="转账">
</form>
</body>
</html>


第三步:根据mvc架构模式创建包


  • com.st.bank.pojo
  • com.st.bank.service
  • com.st.bank.service.impl
  • com.st.bank.dao->使用mybatis框架使 dao常被命名为dao
  • com.st.bank.dao.impl
  • com.st.bank.web.controller
  • com.st.bank.utils
  • com.st.bank.exception


第四步:定义pojo类


Account


package com.st.bank.pojo;
/**
 * @author: TIKI
 * @Project: mybatis -Account
 * @Pcakage: com.st.bank.pojo.Account
 * @Date: 2022年11月12日 20:18
 * @Description:银行账户类
 */
public class Account {
    private Long id;
    private String actno;
    private Double balance;
    public Account() {
    }
    public Account(Long id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getActno() {
        return actno;
    }
    public void setActno(String actno) {
        this.actno = actno;
    }
    public Double getBalance() {
        return balance;
    }
    public void setBalance(Double balance) {
        this.balance = balance;
    }
    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", actno='" + actno + '\'' +
                ", balance=" + balance +
                '}';
    }
}


第五步:编写AccountDao接口以及AccountDanImp实现类


  • 分析dao中至少需要提供几个方法,才能完成转账


。转账前需要查询余额是否充足:selectByActno

。转账时要更新账户:update


  • AccountDao接口


package com.st.bank.dao;
import com.st.bank.pojo.Account;
/**
 * 账户的DAO对象,负责t_act表中数据的CRUD
 */
public interface AccountDao {
    int updateAccount(Account account);
    Account selectByActno(String actno);
}


  • AccountDanImp实现类


package com.st.bank.dao.impl;
import com.st.bank.dao.AccountDao;
import com.st.bank.pojo.Account;
import com.st.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
/**
 * @author: TIKI
 * @Project: mybatis -AccountDaoImpl
 * @Pcakage: com.st.bank.dao.impl.AccountDaoImpl
 * @Date: 2022年11月15日 16:12
 * @Description:
 */
public class AccountDaoImpl implements AccountDao {
    @Override
    public int updateAccount(Account account) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.update("account.updateAccount",account);
        sqlSession.commit();
        sqlSession.close();
        return  count;
    }
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        sqlSession.close();
        return account;
    }
}


第六步:编写SQL映射文件


  • 根据Dao接口编写sql语句


。selectByActno

。update


<?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="account">
    <select id="selectByActno" resultType="com.st.bank.pojo.Account">
        select * from t_act where actno = #{actno};
    </select>
    <update id="updateAccount" >
        update t_act set balance = #{balance} where actno = #{actno};
    </update>
</mapper>


第七步:编写AccountService接口以及AccountServiceImpl实现类


  • AccountService接口


package com.st.bank.service;
import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;
/**
 * 账户业务类
 */
public interface AccountService {
    /** 账户转账业务
     * @param fromActno 转出账户
     * @param toActno 转入账户
     * @param money 转账金额
     */
    void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException;
}


  • AccountServiceImpl实现类


package com.st.bank.service.impl;
import com.st.bank.dao.AccountDao;
import com.st.bank.dao.impl.AccountDaoImpl;
import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;
import com.st.bank.pojo.Account;
import com.st.bank.service.AccountService;
/**
 * @author: TIKI
 * @Project: mybatis -AccountServiceImpl
 * @Pcakage: com.st.bank.service.impl.AccountServiceImpl
 * @Date: 2022年11月15日 16:03
 * @Description:
 */
public class AccountServiceImpl implements AccountService {
    private  AccountDao accountDao = new AccountDaoImpl();
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
        // 1. 判断转出账户的余额是否充足(select)
        Account fromAccount = accountDao.selectByActno(fromActno);
        if (fromAccount.getBalance() < money){
            // 2. 如果转出账户余额不足,提示用户exception
            throw new MoneyNotEnoughException("对不起,余额不足");
        }
        // 3. 如果转出账户余额充足,更新转出账户余额(update)
        fromAccount.setBalance(fromAccount.getBalance() - money);
        int count = accountDao.updateAccount(fromAccount);
        // 4. 更新转入账户余额(update)
        Account toAccount = accountDao.selectByActno(toActno);
        toAccount.setBalance(toAccount.getBalance() + money);
        count += accountDao.updateAccount(toAccount);
        if (count != 2) {
            throw new TransferException("转账失败");
        }
    }
}


  • MoneyNotEnoughException异常


package com.st.bank.exceptions;
/**
 * @author: TIKI
 * @Project: mybatis -MoneyNotEnoughException
 * @Pcakage: com.st.bank.exceptions.MoneyNotEnoughException
 * @Date: 2022年11月15日 20:52
 * @Description:
 */
public class MoneyNotEnoughException extends Exception{
    public MoneyNotEnoughException() {}
    public MoneyNotEnoughException(String message) {
        super(message);
    }
}


  • TransferException异常


package com.st.bank.exceptions;
/**
 * @author: TIKI
 * @Project: mybatis -TransferException
 * @Pcakage: com.st.bank.exceptions.TransferException
 * @Date: 2022年11月15日 21:03
 * @Description:转账异常
 */
public class TransferException extends Exception{
    public TransferException() {}
    public TransferException(String message) {
        super(message);
    }
}


第八步:编写AccountController


package com.st.bank.web;
import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;
import com.st.bank.service.AccountService;
import com.st.bank.service.impl.AccountServiceImpl;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * @author: TIKI
 * @Project: mybatis -AccountServlet
 * @Pcakage: com.st.bank.web.AccountServlet
 * @Date: 2022年11月15日 15:55
 * @Description:
 */
@WebServlet("/transfer")
public class AccountServlet extends HttpServlet {
    // 为了让变量在其他方法也能使用
    private AccountService accountService = new AccountServiceImpl();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 1.获取表单数据
        String fromActno = request.getParameter("fromActno");
        String toActno = request.getParameter("toActno");
        double money = Double.parseDouble(request.getParameter("money"));
        try {
            // 2.调用service的转账方法完成转账(调业务层)
            accountService.transfer(fromActno, toActno, money);
            // 3.调用视图层进行结果展示
            response.sendRedirect(request.getContextPath() + "/success.html");
        } catch (MoneyNotEnoughException e) {
            response.sendRedirect(request.getContextPath() + "/error1.html");
        } catch (TransferException e) {
            response.sendRedirect(request.getContextPath() + "/error2.html");
        }
    }
}


6.4 MyBatis对象作用域以及事务管理器


6.4.1 MyBatis核心对象的作用域


SqlSessionFactoryBuilder


  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。


  • 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)


  • 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。


SqlSessionFactory


  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。


  • 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。


  • 因此 SqlSessionFactory 的最佳作用域是应用作用域application。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。


SqlSession


  • 每个线程都应该有它自己的 SqlSession 实例。


  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求request或方法作用域。

。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。

。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。


  • 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。


6.4.2 事务问题


  • 在之前的转账业务中,更新了两个账户,我们需要保证它们的同时成功或同时失败,这个时候就需要使用事务机制,在transfer方法开始执行时开启事务,直到两个更新都成功之后,再提交事务


  • 当出现异常时,两个账户的更新一个失败一个成功就出现了事务问题

。原因:service和dao中使用的SqlSession对象不是同一个


6.4.3 简单的ThreadLocal


ThreadLocal:实际上是一下Map集合


package com.powernode.threadlocal;
import java.util.HashMap;
import java.util.Map;
/**
 * 自定义一个ThreadLocal类
 */
public class MyThreadLocal<T> {
    /**
     * 所有需要和当前线程绑定的数据要放到这个容器当中
     */
    private Map<Thread, T> map = new HashMap<>();
    /**
     * 向ThreadLocal中绑定数据
     */
    public void set(T obj){
        map.put(Thread.currentThread(), obj);
    }
    /**
     * 从ThreadLocal中获取数据
     * @return
     */
    public T get(){
        return map.get(Thread.currentThread());
    }
    /**
     * 移除ThreadLocal当中的数据
     */
    public void remove(){
        map.remove(Thread.currentThread());
    }
}


6.4.4 事务的解决方法【重要】


  • 为了保证service和dao中使用的SqlSession对象是同一个,可以将SqlSession对象存放到 ThreadLocal当中【保证一个线程对应一个SqlSession】


。修改SqlSessionUtil工具类:将SqlSession对象存放到 ThreadLocal当中,并添加close函数


package com.st.bank.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
/**
 * @author: TIKI
 * @Project: mybatis -SqlSessionUtil
 * @Pcakage: com.st.mybatis.utils.SqlSessionUtil
 * @Date: 2022年10月29日 14:53
 * @Description:MyBatis工具类
 */
public class SqlSessionUtil {
    private SqlSessionUtil(){};// 工具类的构方法私有化,防止new对象
    private static SqlSessionFactory sqlSessionFactory;
    // 类加载时执行
    // SqlSessionUtil工具类在进行第一次加载的时候,解析mybatis-config.xml文件,创建SqlSessionFactory对象
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();
    /**
     * @return 返回一个SqlSession对象
     */
    public static SqlSession openSession(){
        SqlSession sqlSession = local.get();
        if (sqlSession == null){
            sqlSession = sqlSessionFactory.openSession();
            // 将sqlSession对象绑定到当前线程
            local.set(sqlSession);
        }
        return sqlSession;
    }
    /** 关闭SqlSession对象(从当前线程中溢出SqlSession对象)
     * @param sqlSession
     */
    public static void  close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
            local.remove();
        }
    }
}


。修改dao中的方法:AccountDaoImpl中所有方法中的提交commit和关闭close代码全部删除


package com.st.bank.dao.impl;
import com.st.bank.dao.AccountDao;
import com.st.bank.pojo.Account;
import com.st.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
/**
 * @author: TIKI
 * @Project: mybatis -AccountDaoImpl
 * @Pcakage: com.st.bank.dao.impl.AccountDaoImpl
 * @Date: 2022年11月15日 16:12
 * @Description:
 */
public class AccountDaoImpl implements AccountDao {
    @Override
    public int updateAccount(Account account) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.update("account.updateAccount",account);
        return  count;
    }
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
        return account;
    }
}


。修改service中的代码:添加事务控制代码(提交事务 关闭事务)


package com.st.bank.service.impl;
import com.st.bank.dao.AccountDao;
import com.st.bank.dao.impl.AccountDaoImpl;
import com.st.bank.exceptions.MoneyNotEnoughException;
import com.st.bank.exceptions.TransferException;
import com.st.bank.pojo.Account;
import com.st.bank.service.AccountService;
import com.st.bank.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
/**
 * @author: TIKI
 * @Project: mybatis -AccountServiceImpl
 * @Pcakage: com.st.bank.service.impl.AccountServiceImpl
 * @Date: 2022年11月15日 16:03
 * @Description:
 */
public class AccountServiceImpl implements AccountService {
    private  AccountDao accountDao = new AccountDaoImpl();
    @Override
    public void transfer(String fromActno, String toActno, double money) throws MoneyNotEnoughException, TransferException {
        // 添加事务控制代码
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 1. 判断转出账户的余额是否充足(select)
        Account fromAccount = accountDao.selectByActno(fromActno);
        if (fromAccount.getBalance() < money){
            // 2. 如果转出账户余额不足,提示用户exception
            throw new MoneyNotEnoughException("对不起,余额不足");
        }
        // 3. 如果转出账户余额充足,更新转出账户余额(update)
        fromAccount.setBalance(fromAccount.getBalance() - money);
        int count = accountDao.updateAccount(fromAccount);
        // 模拟异常
//        String s = null;
//        s.toString();
        // 4. 更新转入账户余额(update)
        Account toAccount = accountDao.selectByActno(toActno);
        toAccount.setBalance(toAccount.getBalance() + money);
        count += accountDao.updateAccount(toAccount);
        if (count != 2) {
            throw new TransferException("转账失败");
        }
        // 提交事务
        sqlSession.commit();
        // 关闭事务
        sqlSession.close();
    }
}


七、使用javassist生成类


7.1 Javassist的使用


  • 引入Javassist依赖


        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.29.1-GA</version>
        </dependency>


  • 生成第一个类


 @Test
    public void testGenerateFirstClass() throws Exception{
        // 获取类池,这个类池就是用来给我生成class的
        ClassPool pool = ClassPool.getDefault();
        // 制造类(需要告诉javassist,类名是啥)
        CtClass ctClass = pool.makeClass("com.st.bank.dao.impl.AccountDaoImpl");
        // 制造方法
        String methodCode = "public void insert(){System.out.println(123);}";
        CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
        // 将方法添加到类中
        ctClass.addMethod(ctMethod);
        // 在内存中生成class
        ctClass.toClass();
        // 类加载到JVM当中,返回AccountDaoImpl类的字节码
        Class<?> clazz = Class.forName("com.st.bank.dao.impl.AccountDaoImpl");
        // 创建对象
        Object obj = clazz.newInstance();
        // 获取AccountDaoImpl中的insert方法
        Method insertMethod = clazz.getDeclaredMethod("insert");
        // 调用方法insert
        insertMethod.invoke(obj);
    }

ccbb3df6138b48140aca62c9bb4f5862.png


未解决


7.2 使用Javassist生成DaoImpl类


  • AccountDao接口


package com.st.bank.dao;
/**
 * 账户的DAO对象,负责t_act表中数据的CRUD
 */
public interface AccountDao {
    void delete();
    int insert(String actno);
    int update(String actno, Double balance);
    String selectByActno(String actno);
}


  • 使用Javassist生成AccountDaoImpl类


@Test
    public void testGenerateAccountDaoImpl() throws Exception{
        // 获取类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass("com.st.bank.dao.impl.AccountDaoImpl");
        // 制造接口
        CtClass ctInterface = pool.makeInterface("com.st.bank.dao.AccountDao");
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口中所有的方法
        // 获取接口中所有的方法
        Method[] methods = AccountDao.class.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            // method是接口中的抽象方法
            // 把method抽象方法给实现了。
            try {
                // public void delete(){}
                // public int update(String actno, Double balance){}
                StringBuilder methodCode = new StringBuilder();
                methodCode.append("public "); // 追加修饰符列表
                methodCode.append(method.getReturnType().getName()); // 追加返回值类型
                methodCode.append(" ");
                methodCode.append(method.getName()); //追加方法名
                methodCode.append("(");
                // 拼接参数 String actno, Double balance
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    methodCode.append(parameterType.getName());
                    methodCode.append(" ");
                    methodCode.append("arg" + i);
                    if(i != parameterTypes.length - 1){
                        methodCode.append(",");
                    }
                }
                methodCode.append("){System.out.println(11111); ");
                // 动态的添加return语句
                String returnTypeSimpleName = method.getReturnType().getSimpleName();
                if ("void".equals(returnTypeSimpleName)) {
                }else if("int".equals(returnTypeSimpleName)){
                    methodCode.append("return 1;");
                }else if("String".equals(returnTypeSimpleName)){
                    methodCode.append("return \"hello\";");
                }
                methodCode.append("}");
                System.out.println(methodCode);
                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        // 在内存中生成class,并且加载到JVM当中
        Class<?> clazz = ctClass.toClass();
        // 创建对象
        AccountDao accountDao = (AccountDao) clazz.newInstance();
        // 调用方法
        accountDao.insert("aaaaa");
        accountDao.delete();
        accountDao.update("aaaa", 1000.0);
        accountDao.selectByActno("aaaa");
    }


7.3 GenerateDaoProxy工具类P62-63


创建GenerateDaoProxy工具类,根据Dao接口,自动生成实现类的字节码文件


凡是使用GenerateDaoProxy的,SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。


package com.st.bank.utils;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
 * @author: TIKI
 * @Project: mybatis -GenerateDaoProxy
 * @Pcakage: com.st.bank.utils.GenerateDaoProxy
 * @Date: 2022年11月17日 20:24
 * @Description:工具类 可以动态的生成DAO的实现类(可以动态生成DAO的代理类)
 */
public class GenerateDaoProxy {
    /**
     * 生成dao接口实现类,并且将实现类的对象创建出来并返回。
     * @param daoInterface dao接口
     * @return dao接口实现类的实例化对象。
     */
    public static Object generate(SqlSession sqlSession, Class daoInterface){
        // 类池
        ClassPool pool = ClassPool.getDefault();
        // 制造类(com.powernode.bank.dao.AccountDao --> com.powernode.bank.dao.AccountDaoProxy)
        CtClass ctClass = pool.makeClass(daoInterface.getName() + "Proxy"); // 实际本质上就是在内存中动态生成一个代理类。
        // 制造接口
        CtClass ctInterface = pool.makeInterface(daoInterface.getName());
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口中所有的方法
        Method[] methods = daoInterface.getDeclaredMethods();
        Arrays.stream(methods).forEach(method -> {
            // method是接口中的抽象方法
            // 将method这个抽象方法进行实现
            try {
                // Account selectByActno(String actno);
                // public Account selectByActno(String actno){ 代码; }
                StringBuilder methodCode = new StringBuilder();
                methodCode.append("public ");
                methodCode.append(method.getReturnType().getName());
                methodCode.append(" ");
                methodCode.append(method.getName());
                methodCode.append("(");
                // 需要方法的形式参数列表
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    Class<?> parameterType = parameterTypes[i];
                    methodCode.append(parameterType.getName());
                    methodCode.append(" ");
                    methodCode.append("arg" + i);
                    if(i != parameterTypes.length - 1){
                        methodCode.append(",");
                    }
                }
                methodCode.append(")");
                methodCode.append("{");
                // 需要方法体当中的代码
                // 包名需要完整包名
                methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.st.bank.utils.SqlSessionUtil.openSession();");
                // 需要知道是什么类型的sql语句
                // sql语句的id是框架使用者提供的,具有多变性。对于我框架的开发人员来说。我不知道。
                // 既然我框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:
                // 凡是使用GenerateDaoProxy机制的。sqlId都不能随便写。namespace必须是dao接口的全限定名称。id必须是dao接口中方法名。
                String sqlId = daoInterface.getName() + "." + method.getName();
                // 获取sql语句的类型
                SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
                if (sqlCommandType == SqlCommandType.INSERT) {
                }
                if (sqlCommandType == SqlCommandType.DELETE) {
                }
                if (sqlCommandType == SqlCommandType.UPDATE) {
                    // 与先前的代码相对应 arg0
                    methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
                }
                if (sqlCommandType == SqlCommandType.SELECT) {
                    String returnType = method.getReturnType().getName();
                    methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
                }
                methodCode.append("}");
                CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
                ctClass.addMethod(ctMethod);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        // 创建对象
        Object obj = null;
        try {
            Class<?> clazz = ctClass.toClass();
            obj = clazz.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }
}


八、MyBatis中接口代理机制及使用


  • 一般使用mybatis的话,一般不叫做XXXDao,叫做XXXMapper


  • 使用代理机制实现xxxMapper接口的实现类


SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。!!!!


//    private  AccountDao accountDao = new AccountDaoImpl();
    // 使用GenerateDaoProxy实现AccountDao
//    private  AccountDao accountDao = (AccountDao) GenerateDaoProxy.generate(SqlSessionUtil.openSession(),AccountDao.class);
    // 使用mybatis的代理类生成dao接口的实现类:在内存中生成dao接口的代理类,然后创建代理类的实例
    // 注意:SQLMapper.xml映射文件中namespace必须是dao接口的全名,id必须是dao接口中的方法名。!!!!
    private  AccountDao accountDao = SqlSessionUtil.openSession().getMapper(AccountDao.class);


九、MyBatis小技巧


9.1 #{}和${}


区别


  • #{}:先编译sql语句,再给占位符?传值,底层是PreparedStatement实现。

。可以防止sql注入,比较常用。


  • ${}:先进行sql语句拼接,然后再编译sql语句,底层是Statement实现。

。存在sql注入现象。只有在需要进行sql语句关键字拼接的情况下才会用到。


  • 优先使用#{},避免sql注入的风险
  • sql注入现象:sql语句的原意被扭曲

。在数据交互中,前端的数据传入到后台处理时,没有做严格的判断,导致其传入的“数据”拼接到SQL语句中后,被当作SQL语句的一部分执行。 从而导致数据库受损(被脱库、被删除、甚至整个服务器权限陷)。【百度百科】


。用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的原意被扭曲,进而导致sql注入


#{}的执行结果:
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ?
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 新能源(String)
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - <==      Total: 2


${}的执行结果:
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源
[main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters:
org.apache.ibatis.exceptions.PersistenceException:
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
### The error may exist in CarMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select             id,             car_num as carNum,             brand,             guide_price as guidePrice,             produce_time as produceTime,             car_type as carType         from             t_car         where             car_type = 新能源
### Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'


什么情况下必须使用${}


拼接关键字


  • ${}如果需要把SQL语句的关键字放到SQL语句中,只能使用${},因为#{}是以值的形式**‘值’**放到SQL语句当中的。


  • 需求:通过向sql语句中注入asc或desc关键字,来完成数据的升序或降序排列。


#{}的执行结果:
Preparing: select
                id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
           from t_car order by produce_time ?
Parameters: asc(String)
select
    id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
from t_car order by produce_time 'asc'


${}的执行结果:
Preparing:
    select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
    from t_car order by produce_time asc
Parameters:


拼接表名


  • 向SQL语句当中拼接表名,就需要使用${}


  • 现实业务当中,可能会存在分表存储数据的情况。因为一张表存的话,数据量太大。查询效率比较低。

可以将这些数据有规律的分表存储,这样在查询的时候效率就比较高。因为扫描的数据量变少了。


  • 日志表:专门存储日志信息的。如果t_log只有一张表,这张表中每一天都会产生很多log,慢慢的,这个表中数据会很多。


。怎么解决问题?


可以每天生成一个新表。每张表以当天日期作为名称,例如:

t_log_20220901

t_log_20220902


。你想知道某一天的日志信息怎么办?

假设今天是20220901,那么直接查:t_log_20220901的表即可。


  • 使用#{}会是这样:select * from t_log_‘20220901’


  • 使用${}会是这样:select * from t_log_20220901


批量删除


  • 批量删除:一次性删除多条记录


  • sql语句写法


。delete from t_user where id= 1 or id= 2 or id= 3;

。delete from t_user where id in (1, 2, 3);


  • delete from t_car where id in (${ids});


模糊查询


  • 需求:查询奔驰系列的汽⻋。【只要品牌brand中含有奔驰两个字的都查询出来。】


  • sql语句写法


    select * from t_car where brand like '%奔驰%';
    select * from t_car where brand like '%比亚迪%';


  • 第一种方案:


select * from t_car where brand like ‘%${brand}%’


。进行sql拼接


  • 第二种方案:concat函数,这个是mysql数据库当中的一个函数,专门进行字符串拼接


select * from t_car where brand like concat(‘%’,#{brand},‘%’)


  • 第三种方案:比较鸡肋了。可以不算。


select * from t_car where brand like concat(‘%’,‘${brand}’,‘%’)


  • 第四种方案:


select * from t_car where brand like “%”#{brand}“%”


9.2 typeAliases


resultType属性用来指定查询结果集的封装类型,这个名字太⻓,可以起别名吗?可以。


在mybatis-config.xml文件中使用typeAliases标签来起别名,包括两种方式:


  • 第一种方式:typeAlias


。type:指定给哪个类型起别名

。alias:指定别名


  • 注意:别名不区分大小写
  • 省略alias后,别名就是类的简名


  • 第二种方式:package


将这个包下的所有的类全部自动起别名,别名就是简类名,不区分大小写


    <typeAliases>
        <!--别名自己指定的-->
        <typeAlias type="com.powernode.mybatis.pojo.Car" alias="aaa"/>
        <typeAlias type="com.powernode.mybatis.pojo.Log" alias="bbb"/>
        <!--采用默认的别名机制-->
        <typeAlias type="com.powernode.mybatis.pojo.Car"/>
        <typeAlias type="com.powernode.mybatis.pojo.Log"/>
        <!--包下所有的类自动起别名。使用简名作为别名。-->
        <package name="com.powernode.mybatis.pojo"/>
    </typeAliases>


  • namespace不能使用别名机制,只能使用全限定接口名称


9.3 mappers


mybatis-config.xml文件中的mappers标签。


mapper标签的作用是指定SqlMapper.xml文件的路径


SQL映射文件的配置方式包括四种:


  • resource:从类的根路径下开始查找资源。


。要求SQL映射文件必须放在resources目录下或其子目录下


  • url:从指定的全限定资源路径中 加载


  • class:使用映射器接口实现类的完全限定类名,必须带有包名的。


。SQL映射文件和mapper接口放在同一个目录下

。SQL映射文件的名字也必须和mapper接口名一致


思考:mapper标签的作用是指定SqlMapper.xml文件的路径,指定接口名有什么用呢?


如果你class指定是:com.powernode.mybatis.mapper.CarMapper


那么mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件。


注意:也就是说:如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下。并且名字一致。


CarMapper接口-> CarMapper.xml

LogMapper接口-> LogMapper.xml


提醒!!!!!!!!!!!!!!!!!!!!!!!

在IDEA的resources目录下新建多重目录的话,必须是这样创建:

com/powernode/mybatis/mapper

不能这样:

com.powernode.mybatis.mapper


  • package:将包内的映射器接⼝实现全部注册为映射器


要求同上


。SQL映射文件和mapper接口放在同一个目录下

。SQL映射文件的名字也必须和mapper接口名一致

 

<mappers>   
  <mapper resource="CarMapper.xml"/> 要求类的根路径下必须有:CarMapper.xml
    <mapper url="file:///d:/CarMapper.xml"/> 要求在d:/下有CarMapper.xml文件
    <mapper class="全限定接口名,带有包名"/>
    <package name="com.powernode.mybatis.mapper"/>
</mappers>


9.4 idea配置文件模板


在File->Settings->Editor->File and Code Templates中添加模板


  • MyBatis核心配置文件 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="" />
    <typeAliases>
        <package name=""/>
    </typeAliases>
    <environments default="stDB">
        <environment id="stDB">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name=""/>      
    </mappers>
</configuration>


  • MyBatisSQL映射文件 XXXMapper.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>


9.5 插入数据时获取自动生成的主键


  • 前提:主键是⾃动生成的。


  • 业务背景:插入一条新的记录之后,⾃动生成了主键,⽽这个主键需要在其他表中使用时。


。比如:一个用户有多个⻆⾊。


  • 代码


    <!--
        useGeneratedKeys="true" 使用自动生成的主键值。
        keyProperty="id" 指定主键值赋值给对象的哪个属性。这个就表示将主键值赋值给Car对象的id属性。
    -->
    <insert id="insertCarUseGeneratedKeys" useGeneratedKeys="true" keyProperty="id">
        insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>


@Test
    public void testInsertCarUseGeneratedKeys(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null,"9991", "凯美瑞", 30.0, "2020-11-11", "燃油车");
        mapper.insertCarUseGeneratedKeys(car);
        System.out.println(car);
        sqlSession.commit();
        sqlSession.close();
    }


十、MyBatis参数处理


Mapper接口中参数的问题


10.1 单个简单类型参数


  • 简单类型包括


。byte short int long float double char

。Byte Short Integer Long Float Double Character

。String

。java.util.Date

。java.sql.Date


  • 简单类型对于mybatis来说都是可以自动类型识别的:


也就是说对于mybatis来说,它是可以自动推断出ps.setXxxx()方法的。ps.setString()还是 ps.setInt()。


  • 完整代码


。其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助 mybatis进行类型确定的。【可以省略】


。parameterType属性:告诉mybatis框架,这个方法的参数类型是什么的


mybatis框架由自动推断机制[获取实际方法调用的参数类型],所以大部分情况下parameterType属性都是可以省略不写的


SQL语句最终是这样的:


select * from t_student where id = ?


JDBC代码是一定要给?传值的。

怎么传值?ps.setXxx(第几个问号, 传什么值);


ps.setLong(1, 1L);
ps.setString(1, “zhangsan”);
ps.setDate(1, new Date());
ps.setInt(1, 100);

mybatis底层到底调用setXxx的哪个方法,取决于parameterType属性的值。


    <select id="selectById" resultType="Student" parameterType="long">
        select * from t_student where id = #{id}
    </select>
  <!--加上Type mybatis不需要做自动类型推断-->
    <select id="selectByName" resultType="student">
        select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
    </select>


  • 如果参数只有一个的话,#{}里面的内容就随便写了。对于${}来说,注意加单引号。


10.2 Map参数


  • 这种方式是⼿动封装Map集合,将每个条件以key和value的形式存放到集合中。然后在使用的时候通过# {map集合的key}来取值。


  • 代码


    <!--<insert id="insertStudentByMap" parameterType="map">-->
    <insert id="insertStudentByMap">
        insert into t_student(id,name,age,sex,birth,height) values(null,#{姓名},#{年龄},#{性别},#{生日},#{身高})
    </insert>

 

    @Test
    public void testInsertStudentByMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Map<String,Object> map = new HashMap<>();
        map.put("姓名", "赵六");
        map.put("年龄", 20);
        map.put("身高", 1.81);
        map.put("性别", '男');
        map.put("生日", new Date());
        mapper.insertStudentByMap(map);
        sqlSession.commit();
        sqlSession.close();
    }


10.3 pojo实体类参数


  • #{}里面写的是属性名字。这个属性名其本质上是:set/get方法名去掉set/get之后 的名字。


  • 代码


    <!--<insert id="insertStudentByPOJO" parameterType="student">-->
    <insert id="insertStudentByPOJO">
        insert into t_student(id,name,age,sex,birth,height) values(null,#{name},#{age},#{sex},#{birth},#{height})
    </insert>

 

@Test
    public void testInsertStudentByPOJO(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        // POJO对象
        Student student = new Student();
        student.setName("张飞");
        student.setAge(50);
        student.setSex('女');
        student.setBirth(new Date());
        student.setHeight(10.0);
        mapper.insertStudentByPOJO(student);
        sqlSession.commit();
        sqlSession.close();
    }


10.4 多参数


  • 需求:使用多个参数一起进行查询


  • 实现原理:实际上在mybatis底层会创建一个map集合,以arg0或者param1为key,以方法上的参数为 value


Map<String,Object> map = new HashMap<>();
map.put("arg0", name);
map.put("arg1", sex);
map.put("param1", name);
map.put("param2", sex);
// 所以可以这样取值:#{arg0} #{arg1} #{param1} #{param2}
// 其本质就是#{map集合的key}


  • 代码


    <!--
        注意:低版本的mybatis中,使用的是:#{0}和#{1},以及#{2}...
        高版本的mybatis中,使用的是:
            #{arg0}
            #{arg1}
            #{arg2}
            #{arg3}
            #{arg4}
            #{param1}
            #{param2}
            #{param3}
            #{param4}
    -->
    <select id="selectByNameAndSex" resultType="Student">
        <!--select * from t_student where name = #{arg0} and sex = #{arg1}-->
        <!--select * from t_student where name = #{param1} and sex = #{param2}-->
        select * from t_student where name = #{arg0} and sex = #{param2}
    </select>


    @Test
    public void testSelectByNameAndSex(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> students = mapper.selectByNameAndSex("张三", '男');
        students.forEach(student -> System.out.println(student));
        sqlSession.close();
    }


10.5 @Param注解(命名参数)


  • 使用 @Param注解即可自定义map集合的key,可以增强可读性。


@Param(“这⾥填写的其实就是map集合的key”)

。使用了@Param注解之后,arg0和arg1失效了

。使用了@Param注解之后,param1和param2还可以用


  • 代码


StudentMapper接⼝


/**
     * 根据name和age查询 
     * value可以省略不写
     * @param name      
     * @param age      
     * @return
     */
      List<Student> selectByNameAndAge(@Param(value="name") String name, @Param("age") int age);


    <select id="selectByNameAndSex2" resultType="Student">
        <!--使用了@Param注解之后,arg0和arg1失效了-->
        <!--select * from t_student where name = #{arg0} and sex = #{arg1}-->
        <!--使用了@Param注解之后,param1和param2还可以用-->
        <!--select * from t_student where name = #{param1} and sex = #{param2}-->
        select * from t_student where name = #{name} and sex = #{sex}
    </select>


@Test
public void testSelectByNameAndSex2(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    // mapper实际上指向了代理对象
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    // mapper是代理对象
    // selectByNameAndSex2是代理方法
    List<Student> students = mapper.selectByNameAndSex2("张三", '男');
    students.forEach(student -> System.out.println(student));
    sqlSession.close();
}


10.6 @Param源码分析


  • 代理模式


。代理对象 中介公司

。代理方法 找房子

。目标对象 我

。目标方法 找房子


  • 源码分析


3c54745e5631e605ba9e0b463168a6e8.png


ae9102c229706d9248265550fcb1d720.png


10.7 数组、集合


十一、MyBatis查询语句返回结果专题


  • select标签的returnType属性,用来指定返回结果的类型


。mybatis为常见的Java类型内建的别名


49de4f635056acf444ee5cb48e0c68e1.png


  • select标签的resultMap属性,用来指定使用哪个结果映射。resultMap后面的值是resultMap的id


11.1 返回pojo类 Car


查询结果是一条的话,返回一个pojo对象


查询结果是一条的话可以使用List集合接收吗?当然可以。


  • 接口


/**
     * 根据id查询Car信息
     * @param id
     * @return
     */
    Car selectById(Long id);


  • mapper.xml


    <!--声明一个SQL片段-->
    <sql id="carColumnNameSql">
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
    </sql>
  <select id="selectById" resultType="car">
        select
        <!--将声明的sql片段包含进来。-->
            <include refid="carColumnNameSql"/>
        from t_car where id = #{id}
    </select>

  • test


@Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(158L);
        System.out.println(car);
        sqlSession.close();
    }


11.2 返回List<Car>


当查询的记录条数是多条的时候,必须使用集合接收。


如果使用单个实体类接收会出现异常TooManyResultsException。


  • mapper接口


    /**
     * 获取所有的Car
     * @return
     */
    List<Car> selectAll();


  • mapper.xml


返回car


    <select id="selectAll" resultType="car">
        select
            <include refid="carColumnNameSql"/>
        from t_car
    </select>


  • test


    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }


11.3 返回Map


当返回的数据,没有合适的实体类对应的话,可以采用Map集合接收。字段名做key,字段值做value。


查询如果可以保证只有一条数据,则返回一个Map集合即可。


ae2ac94b411197ed28fa95463abc406e.png


  • 接口


/**
     * 根据id获取汽车信息。将汽车信息放到Map集合中。
     * +-----+---------+----------+-------------+--------------+----------+
     * | id  | car_num | brand    | guide_price | produce_time | car_type |
     * +-----+---------+----------+-------------+--------------+----------+
     * | 158 | 1111    | 比亚迪汉 |        3.00 | 2000-10-10   | 新能源   |
     * +-----+---------+----------+-------------+--------------+----------+
     *
     * Map<String, Object>
     *     k                 v
     *     -----------------------
     *     "id"             158
     *     "car_num"        1111
     *     "brand"          比亚迪汉
     *     ....
     *
     * @param id
     * @return
     */
    Map<String, Object> selectByIdRetMap(Long id);


  • xml


    <select id="selectByIdRetMap" resultType="map">
        select * from t_car where id = #{id}
    </select>


  • test


    @Test
    public void testSelectByIdRetMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Map<String, Object> car = mapper.selectByIdRetMap(158L);
        System.out.println(car);
        sqlSession.close();
    }


11.4 返回List<Map>


查询结果条数⼤于等于1条数据,则可以返回一个存储Map集合的List集合。List<Map>等同于List<Car>


d6e39ca03f88505bb16231a31cf08c1b.png


  • mapper接口


    @Test
    public void testSelectAllRetListMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Map<String, Object>> maps = mapper.selectAllRetListMap();
        maps.forEach(map -> System.out.println(map));
        sqlSession.close();
    }


  • mapper.xml


map


    <!--这个resultType不是list,是map-->
    <select id="selectAllRetListMap" resultType="map">
        select * from t_car
    </select>


  • test


    @Test
    public void testSelectAllRetListMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Map<String, Object>> maps = mapper.selectAllRetListMap();
        maps.forEach(map -> System.out.println(map));
        sqlSession.close();
    }

11.5 返回Map<String,Map>


返回一个大的Map集合:拿Car的id做key,以后取出对应的Map集合时更方便。



bec568195f876ffb99026e06a7286af6.png



  • mapper接口


 /**
     * 查询所有的Car,返回一个大Map集合。
     * Map集合的key是每条记录的主键值。
     * Map集合的value是每条记录。
     * {
     *      160={car_num=3333, id=160, guide_price=32.00, produce_time=2000-10-10, brand=奔驰E300L, car_type=新能源},
     *      161={car_num=4444, id=161, guide_price=32.00, produce_time=2000-10-10, brand=奔驰C200, car_type=新能源},
     *      162={car_num=9999, id=162, guide_price=30.00, produce_time=2020-10-11, brand=帕萨特, car_type=燃油车},
     *      163={car_num=9991, id=163, guide_price=30.00, produce_time=2020-11-11, brand=凯美瑞, car_type=燃油车},
     *      158={car_num=1111, id=158, guide_price=3.00, produce_time=2000-10-10, brand=比亚迪汉, car_type=新能源},
     *      159={car_num=2222, id=159, guide_price=32.00, produce_time=2000-10-10, brand=比亚迪秦, car_type=新能源}
     * }
     * @return
     */
    @MapKey("id") // 将查询结果的id值作为整个大Map集合的key。
    Map<Long, Map<String,Object>> selectAllRetMap();


  • mapper.xml


    <select id="selectAllRetMap" resultType="map">
        select * from t_car
    </select>


  • test


    @Test
    public void testSelectAllRetMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Map<Long, Map<String, Object>> map = mapper.selectAllRetMap();
        System.out.println(map);
        sqlSession.close();
    }


11.6 resultMap结果映射


  • 查询结果的列名和java对象的属性名对应不上怎么办?


。第一种方式:as给列起别名


。第二种方式:使用resultMap进行结果映射


。第三种方式:是否开启驼峰命名自动映射(配置settings)


使用resultMap进行结果映射 【重要】


select标签的resultMap属性,用来指定使用哪个结果映射。


resultMap:指定数据库表的字段名和Java类的属性名的对应关系


  • type属性:用来指定POJO类的类名。


  • id属性:指定resultMap的唯一标识。这个id将来要在select标签中使用。


  • id标签:如果有主键,建议这里配置一个id标签,注意:这不是必须的。


。但是官方的解释是什么呢?这样的配置可以让mybatis提高效率。


  • result标签


。property后面填写POJO类的属性名

。column后面填写数据库表的字段名

。javaType

。jdbcType


  • association标签:关联,多对一


一个Student对象关联一个Clazz对象


。property属性:提供要映射的POJO类的属性名

。javaType属性:用来指定要映射的java类型。

。id标签

。result标签


分布查询时需使用的属性


。property属性:提供要映射的POJO类的属性名

。select属性:关联对象中对应的sql语句的id

。column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名

。fetchType属性:lazy 支持延迟加载


  • collection标签:集合;一对多


一个Clazz对象关联一个Student对象


。property属性:提供要映射集合的属性名。

。ofType 属性:用来指定集合当中的元素类型


分布查询时需使用的属性名


。property属性:提供要映射的集合的属性名

。select属性:关联对象中对应的sql语句的id

。column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名

。fetchType属性:lazy 支持延迟加载


代码


  • mapper接口


    /**
     * 查询所有的Car信息。使用resultMap标签进行结果映射。
     * @return
     */
    List<Car> selectAllByResultMap();


  • mapper.xml


    <!--
        1.专门定义一个结果映射,在这个结果映射当中指定数据库表的字段名和Java类的属性名的对应关系。
        2. type属性:用来指定POJO类的类名。
        3. id属性:指定resultMap的唯一标识。这个id将来要在select标签中使用。
    -->
    <resultMap id="carResultMap" type="Car">
        <!--如果数据库表中有主键,一般都是有主键,要不然不符合数据库设计第一范式。-->
        <!--如果有主键,建议这里配置一个id标签,注意:这不是必须的。但是官方的解释是什么呢?这样的配置可以让mybatis提高效率。-->
        <id property="id" column="id"/>
        <!--<result property="id" column="id"/>-->
        <!--property后面填写POJO类的属性名-->
        <!--column后面填写数据库表的字段名-->
        <result property="carNum" column="car_num" javaType="java.lang.String" jdbcType="VARCHAR"/>
        <!--如果column和property是一样的,这个可以省略。-->
        <!--<result property="brand" column="brand"/>-->
        <result property="guidePrice" column="guide_price"/>
        <result property="produceTime" column="produce_time"/>
        <result property="carType" column="car_type" javaType="string" jdbcType="VARCHAR"/>
    </resultMap>
    <!--select标签的resultMap属性,用来指定使用哪个结果映射。resultMap后面的值是resultMap的id-->
    <select id="selectAllByResultMap" resultMap="carResultMap">
        select * from t_car
    </select>


  • test


    @Test
    public void testSelectAllByResultMap(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAllByResultMap();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }


是否开启驼峰命名自动映射


  • 使用这种方式的前提是:属性名遵循Java的命名规范,数据库表的列名遵循SQL的命名规范。


。Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式。

。SQL命名规范:全部小写,单词之间采用下划线分割。


  • 在mybatis-config.xml文件中进行配置


<!--放在properties标签后⾯-->
<settings>
  <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>


十二、动态SQL


  • 需求:有些SQL语句需要进行动态拼接
  • 使用场景


。批量删除

。多条件查询


12.1 if 标签


if标签


1.if标签中test属性是必须的。

2.if标签中test属性的值是false或者true。

3.如果test是true,则if标签中的sql语句就会拼接。反之,则不会拼接。

4.test属性中可以使用的是:


。当使用了@Param注解,那么test中要出现的是@Param注解指定的参数名。@Param(“brand”),那么这里只能使用brand


。当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2…


。当使用了POJO,那么test中出现的是POJO类的属性名


5.在mybatis的动态SQL当中,不能使用&&,只能使用and。


代码


  • mapper接口


/**
     * 多条件查询
     * @param brand 品牌
     * @param guidePrice 指导价
     * @param carType 汽车类型
     * @return
     */
    List<Car> selectByMultiCondition(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);


  • mapper.xml


1 = 1使sql语句恒成立


    <select id="selectByMultiCondition" resultType="Car">
        select * from t_car where 1 = 1
        <if test="brand != null and brand != ''">
            and brand like "%"#{brand}"%"
        </if>
        <if test="guidePrice != null and guidePrice != ''">
            and guide_price > #{guidePrice}
        </if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        </if>
    </select>


  • test


    @Test
    public void testSelectByMultiCondition(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 假设三个条件都不是空
        //List<Car> cars = mapper.selectByMultiCondition("比亚迪", 2.0, "新能源");
        // 假设三个条件都是空
        //List<Car> cars = mapper.selectByMultiCondition("", null, "");
        // 假设后两个条件不为空,第一个条件为空
        //List<Car> cars = mapper.selectByMultiCondition("", 2.0, "新能源");
        // 假设第一个条件不是空,后两个条件是空
        List<Car> cars = mapper.selectByMultiCondition("比亚迪", null, "");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }


12.2 where标签


where标签的作用:让where子句更加动态智能。


  • 所有条件都为空时,where标签保证不会生成where⼦句。
  • 自动去除某些条件前面多余的and或or,后面多余的不会去除


代码


  • mapper接口


/**
     * 使用where标签,让where子句更加的智能。
     * @param brand
     * @param guidePrice
     * @param carType
     * @return
     */
    List<Car> selectByMultiConditionWithWhere(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);


  • mapper.xml


    <select id="selectByMultiConditionWithWhere" resultType="Car">
        select * from t_car
        <!--where标签是专门负责where子句动态生成的。-->
        <where>
            <if test="brand != null and brand != ''">
                and brand like "%"#{brand}"%"
            </if>
            <if test="guidePrice != null and guidePrice != ''">
                and guide_price > #{guidePrice}
            </if>
            <if test="carType != null and carType != ''">
                and car_type = #{carType}
            </if>
        </where>
    </select>

  • test


    @Test
    public void testSelectByMultiConditionWithWhere(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 三个条件都不是空
        //List<Car> cars = mapper.selectByMultiConditionWithWhere("比亚迪", 2.0, "新能源");
        // 三个条件都是空
        //List<Car> cars = mapper.selectByMultiConditionWithWhere("", null, "");
        // 如果第一个条件是空
        //List<Car> cars = mapper.selectByMultiConditionWithWhere("", 2.0, "新能源");
        // 后面两个条件是空
        List<Car> cars = mapper.selectByMultiConditionWithWhere("比亚迪", null, "");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }


12.3 trim标签


trim标签的属性:


  • prefix:在trim标签中的语句前添加内容
  • suffix:在trim标签中的语句后添加内容
  • prefixOverrides:前缀覆盖掉(去掉)
  • suffixOverrides:后缀覆盖掉(去掉)


代码


  • mapper接口


 /**
     * 使用trim标签
     * @param brand
     * @param guidePrice
     * @param carType
     * @return
     */
    List<Car> selectByMultiConditionWithTrim(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);


  • mapper.xml


   <select id="selectByMultiConditionWithTrim" resultType="Car">
        select * from t_car
        <!--
            prefix:加前缀
            suffix:加后缀
            prefixOverrides:删除前缀
            suffixOverrides:删除后缀
        -->
        <!--prefix="where" 是在trim标签所有内容的前面添加 where-->
        <!--suffixOverrides="and|or" 把trim标签中内容的后缀and或or去掉-->
        <trim prefix="where" suffixOverrides="and|or">
            <if test="brand != null and brand != ''">
                brand like "%"#{brand}"%" or
            </if>
            <if test="guidePrice != null and guidePrice != ''">
                guide_price > #{guidePrice} and
            </if>
            <if test="carType != null and carType != ''">
                car_type = #{carType}
            </if>
        </trim>
    </select>


  • test


    @Test
    public void testSelectByMultiConditionWithTrim(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectByMultiConditionWithTrim("比亚迪", null, "");
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }


12.4 set标签


主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的“,”


比如我们只更新提交的不为空的字段,如果提交的数据是空或者"",那么这个字段我们将不更新。


代码


  • mapper接口


/**
     * 使用set标签
     * @param car
     * @return
     */
    int updateBySet(Car car);


  • mapper.xml


    <update id="updateBySet">
        update t_car
        <set>
            <if test="carNum != null and carNum != ''">car_num = #{carNum},</if>
            <if test="brand != null and brand != ''">brand = #{brand},</if>
            <if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if>
            <if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
            <if test="carType != null and carType != ''">car_type = #{carType},</if>
        </set>
        where
            id = #{id}
    </update>

不使用set标签


    <update id="updateById">
        update t_car set
            car_num = #{carNum},
            brand = #{brand},
            guide_price = #{guidePrice},
            produce_time = #{produceTime},
            car_type = #{carType}
        where
            id = #{id}
    </update>


  • test


    @Test
    public void testUpdateBySet(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(158L, null,"丰田霸道",null,null,null);
        mapper.updateBySet(car);
        sqlSession.commit();
        sqlSession.close();
    }


12.5 choose when otherwise


语法格式:等同于 if elseif else


<choose>
    <when></when>
    <when></when>
    <when></when>
  <otherwise></otherwise>
 </choose>


  • mapper接口


 /**
     * 使用choose when otherwise标签。
     * @param brand
     * @param guidePrice
     * @param carType
     * @return
     */
    List<Car> selectByChoose(@Param("brand") String brand, @Param("guidePrice") Double guidePrice, @Param("carType") String carType);


  • mapper.xml


    <select id="selectByChoose" resultType="Car">
        select * from t_car
        <where>
            <choose>
                <when test="brand != null and brand != ''">
                    brand like "%"#{brand}"%"
                </when>
                <when test="guidePrice != null and guidePrice != ''">
                    guide_price > #{guidePrice}
                </when>
                <otherwise>
                    car_type = #{carType}
                </otherwise>
            </choose>
        </where>
    </select>


  • test


    @Test
    public void testSelectByChoose(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 三个条件都不为空
        //List<Car> cars = mapper.selectByChoose("丰田霸道",1.0,"新能源");
        // 第一个条件是空
        //List<Car> cars = mapper.selectByChoose(null,1.0,"新能源");
        // 前两个条件都是空
        //List<Car> cars = mapper.selectByChoose(null,null,"新能源");
        // 全部都是空
        List<Car> cars = mapper.selectByChoose(null,null,null);
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }

 

12.6 foreach标签


循环数组或集合,动态生成sql


  • foreach标签的属性:


。collection:指定数组或者集合

。item:代表数组或集合中的元素

。separator:循环之间的分隔符

。open: foreach循环拼接的所有sql语句的最前面以什么开始。

。close: foreach循环拼接的所有sql语句的最后面以什么结束。


批量删除


  • mapper接口


    /**
     * 批量删除。foreach标签
     * @param ids
     * @return
     */
    int deleteByIds(@Param("ids") Long[] ids);

  • mapper.xml


    <delete id="deleteByIds">
        <!--
    delete from t_car where id in (id1,id2,id3)
        -->
        delete from t_car where id in
        <foreach collection="ids" item="id" separator="," open="(" close=")">
            #{id}
        </foreach>
    </delete>
    <delete id="deleteByIds2">
        delete from t_car where
        <foreach collection="ids" item="id" separator="or">
            id=#{id}
        </foreach>
    </delete>


  • test


    @Test
    public void testDeleteByIds(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Long[] ids = {158L,159L,160L};
        int count = mapper.deleteByIds(ids);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }


批量添加


  • mapper接口


    /**
     * 批量插入,一次插入多条Car信息
     * @param cars
     * @return
     */
    int insertBatch(@Param("cars") List<Car> cars);


  • mapper.xml


    <insert id="insertBatch">
        insert into t_car values
        <foreach collection="cars" item="car" separator=",">
            (null,#{car.carNum},#{car.brand},#{car.guidePrice},#{car.produceTime},#{car.carType})
        </foreach>
    </insert>


  • test


    @Test
    public void testInsertBatch(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car1 = new Car(null,"1200", "帕萨特1", 30.0, "2020-11-11", "燃油车");
        Car car2 = new Car(null,"1201", "帕萨特2", 30.0, "2020-11-11", "燃油车");
        Car car3 = new Car(null,"1202", "帕萨特3", 30.0, "2020-11-11", "燃油车");
        List<Car> cars = new ArrayList<>();
        cars.add(car1);
        cars.add(car2);
        cars.add(car3);
        mapper.insertBatch(cars);
        sqlSession.commit();
        sqlSession.close();
    }


12.7 sql标签与include标签


  • sql标签用来声明sql片段
  • include标签用来将声明的sql片段包含到某个sql语句当中
  • 作用:

。代码复用

。易维护


代码


  • mapper接口


  • mapper.xml


    <!--声明一个SQL片段-->
    <sql id="carColumnNameSql">
        id,
        car_num as carNum,
        brand,
        guide_price as guidePrice,
        produce_time as produceTime,
        car_type as carType
    </sql>
  <select id="selectById" resultType="car">
        select
        <!--将声明的sql片段包含进来。-->
            <include refid="carColumnNameSql"/>
        from t_car where id = #{id}
    </select>


  • test


十三、MyBatis的高级映射及延迟加载


  • 高级映射


。多对一

。一对多

。多对多:分解成两个一对多

。一对一


  • 怎么区分主表和副表?


谁在前谁是主表->JVM中的主对象


。多对一:多是主表

。一对多:一是主表


  • 多对一


。在主对象类中加一个副对象的属性


cd04abd08ce486b533a4bd691a611dbf.png


  • 一对多


。一对多的实现,通常是在一的一方中有List集合属性


比如在Clazz类中添加List<Student> stus; 属性


d2ce74a84f1a06a6beabf186513693e1.png


13.1 多对一


许多学生对应一个班级


  • 案例:根据学生的编号查询对应的班级信息【班级id+班级名称】


实现方式有多种,常见的包括三种:


  • 第一种方式:一条SQL语句,级联属性映射


1.在pojo类中添加副对象属性

2.编写sql语句:使用左外连接

3.编写resultMap


  • 第二种方式:一条SQL语句,association标签。


1.在pojo类中添加副对象属性


2.编写sql语句:使用左外连接


3.与方式一区别在于resultMap,将关联对象使用association标签进行映射


association:翻译为关联。一个Student对象关联一个Clazz对象


。property:提供要映射的POJO类的属性名。

。javaType:用来指定要映射的java类型。


  • 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加载/延迟加载。)


。方法:


1.在pojo类中添加副对象属性

2.编写sql语句:使用左外连接

3.在association标签中映射关联对象,并配置以下属性

  • property属性:提供要映射的POJO类的属性名
  • select属性:关联对象中对应的sql语句的id
  • column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名
  • fetchType属性:lazy 支持延迟加载


。分步查询的优点:


  • 第一:复用性增强。可以重复利用。(大步拆成N多个小碎步。每一个小碎步更加可以重复利用。)
  • 第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制。


延迟加载


  • 什么是延迟加载(懒加载),有什么用?


。延迟加载的核心原理是:用的时候再执行查询语句。不用的时候不查询。

。作用:提高性能。尽可能的不查,或者说尽可能的少查。来提高效率。


  • 在mybatis当中怎么开启延迟加载呢?


。association标签中添加fetchType=“lazy”


注意:默认情况下是没有开启延迟加载的。需要设置:fetchType=“lazy”


这种在association标签中配置fetchType=“lazy”,是局部的设置,只对当前的association关联的sql语句起作用。


。在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制:


在mybatis核心配置文件中添加全局配置:lazyLoadingEnabled=true


  • 实际开发中的模式:


。把全局的延迟加载打开。

。如果某一步不需要使用延迟加载,请设置:fetchType=“eager”


  • 懒加载执行效果对比:如果需要使用到学生所在班级的名称,这个时候才会执行关联的sql语句


。不使用cname


1b81494ec52b085a96ede641bbdad136.png


。使用cname


38956b3d763f7176fbf711898583a386.png


第一种方式:级联属性映射


代码


  • Student pojo类:添加clazz属性


package com.powernode.mybatis.pojo;
import com.powernode.mybatis.mapper.ClazzMapper;
/**
 * 学生信息
 */
public class Student { // Student是多的一方
    private Integer sid;
    private String sname;
    private Clazz clazz; // Clazz是少的一方。
    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                ", clazz=" + clazz +
                '}';
    }
    public Clazz getClazz() {
        return clazz;
    }
    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }
    //省略其他get set...
}


  • StudentMapper接口


/**
 * 根据id获取学生信息。同时获取学生关联的班级信息。
 * @param id 学生的id
 * @return 学生对象,但是学生对象当中含有班级对象。
 */
Student selectById(Integer id);


  • StudentMapper.xml


    <!--多对一映射的第一种方式:一条SQL语句,级联属性映射。左外连接-->
    <resultMap id="studentResultMap" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <result property="clazz.cid" column="cid"/>
        <result property="clazz.cname" column="cname"/>
    </resultMap>
    <select id="selectById" resultMap="studentResultMap">
        select
            s.sid,s.sname,c.cid,c.cname
        from
            t_stu s left join t_clazz c on s.cid = c.cid
        where
            s.sid = #{sid}
    </select>


  • test


    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectById(1);
        System.out.println(student);
        sqlSession.close();
    }


第二种方式:association


代码


  • StudentMapper接口


    /**
     * 一条SQL语句,association
     * @param id
     * @return
     */
    Student selectByIdAssociation(Integer id);


  • StudentMapper.xml


    <!--一条SQL语句,association。-->
    <resultMap id="studentResultMapAssociation" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <!--
            association:翻译为关联。一个Student对象关联一个Clazz对象
                property:提供要映射的POJO类的属性名。
                javaType:用来指定要映射的java类型。
        -->
        <association property="clazz" javaType="Clazz">
            <id property="cid" column="cid"/>
            <result property="cname" column="cname"/>
        </association>
    </resultMap>
    <select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
        select
            s.sid,s.sname,c.cid,c.cname
        from
            t_stu s left join t_clazz c on s.cid = c.cid
        where
            s.sid = #{sid}
    </select>

  • test


    @Test
    public void testSelectByIdAssociation(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectByIdAssociation(4);
        System.out.println(student);
        sqlSession.close();
    }


第三种方式:分步查询


代码


  • StudentMapper接口


/**
 * 根据班级编号查询学生信息。
 * @param cid
 * @return
 */
List<Student> selectByCidStep2(Integer cid);
/**
 * 分部查询第一步:先根据学生的sid查询学生的信息。
 * @param sid
 * @return
 */
Student selectByIdStep1(Integer sid);


  • StudentMapper.xml


    <select id="selectByCidStep2" resultType="Student">
        select * from t_stu where cid = #{cid}
    </select>
    <!--两条SQL语句,完成多对一的分步查询。-->
    <!--这里是第一步:根据学生的id查询学生的所有信息。这些信息当中含有班级id(cid)-->
    <resultMap id="studentResultMapByStep" type="Student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <association property="clazz"
                     select="com.powernode.mybatis.mapper.ClazzMapper.selectByIdStep2"
                     column="cid"
                     fetchType="eager"/>
    </resultMap>
    <select id="selectByIdStep1" resultMap="studentResultMapByStep">
        select sid,sname,cid from t_stu where sid = #{sid}
    </select>

  • ClazzMapper接口


    /**
     * 分步查询第二步:根据cid获取班级信息。
     * @param cid
     * @return
     */
    Clazz selectByIdStep2(Integer cid);


  • ClazzMapper.xml


    <!--分步查询第二步:根据cid获取班级信息。-->
    <select id="selectByIdStep2" resultType="Clazz">
        select cid,cname from t_clazz where cid = #{cid}
    </select>


  • StudentMapperTest


    @Test
    public void testSelectByIdStep1(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectByIdStep1(5);
        //System.out.println(student);
        // 只需要看学生的名字
        System.out.println(student.getSname());
        // 程序执行到这里了,我想看看班级的名字
        //System.out.println(student.getClazz().getCname());
        sqlSession.close();
    }


代码


  • mapper接口
  • mapper.xml
  • test
  • StudentMapper接口
  • StudentMapper.xml
  • ClazzMapper接口
  • ClazzMapper.xml
  • StudentMapperTest


13.2 一对多


  • 注意:实际开发中不能Student包含Clazz,Clazz包含Student,输出为null可以,不能两端都有值


4a3b20bf55f0e137b509588f1af137ab.png


  • 案例:根据班级编号查询班级信息【班级中的学生编号及姓名】


  • 一对多的实现,通常是在一的一方中有List集合属性。

在Clazz类中添加List<Student> stus; 属性。


d2ce74a84f1a06a6beabf186513693e1.png


  • 实现方式


。collection


1.在主对象类中添加List<副对象>属性


2.编写sql语句:使用左外连接


3.将集合对象使用collection标签进行映射


collection:翻译为集合。比如一个Clazz对象对应一个Student集合


  • property属性:提供要映射集合的属性名。
  • ofType 属性:用来指定集合当中的元素类型。


。分布查询


1.在主对象类中添加List<副对象>属性

2.编写sql语句:使用左外连接

3.将集合对象使用collection标签进行映射并配置以下属性


  • property属性:提供要映射的集合的属性名
  • select属性:关联对象中对应的sql语句的id
  • column属性:select属性对应的sql语句中的执行条件,上步中返回的结果中所需要的字段名
  • fetchType属性:lazy 支持延迟加载


  • clazz pojo类代码


package com.powernode.mybatis.pojo;
import java.util.List;
/**
 * 班级信息
 */
public class Clazz {
    private Integer cid;
    private String cname;
    private List<Student> stus;
    @Override
    public String toString() {
        return "Clazz{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                ", stus=" + stus +
                '}';
    }
    public List<Student> getStus() {
        return stus;
    }
    public void setStus(List<Student> stus) {
        this.stus = stus;
    }
    public Integer getCid() {
        return cid;
    }
    public void setCid(Integer cid) {
        this.cid = cid;
    }
    public String getCname() {
        return cname;
    }
    public void setCname(String cname) {
        this.cname = cname;
    }
    public Clazz() {
    }
    public Clazz(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }
}


第一种方式:collection


代码


  • ClazzMapper接口


    /**
     * 根据班级编号查询班级信息。
     * @param cid
     * @return
     */
    Clazz selectByCollection(Integer cid);


  • ClazzMapper.xml


<resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!--一对多,这里是collection。collection是集合的意思。-->
        <!--ofType 属性用来指定集合当中的元素类型。-->
        <collection property="stus" ofType="Student">
            <id property="sid" column="sid"/>
            <result property="sname" column="sname"/>
        </collection>
    </resultMap>
    <select id="selectByCollection" resultMap="clazzResultMap">
        select c.cid,c.cname,s.sid,s.sname from t_clazz c left join t_stu s on c.cid = s.cid where c.cid = #{cid}
    </select>


  • ClazzMapperTest


@Test
    public void testSelectByCollection(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
        Clazz clazz = mapper.selectByCollection(1000);
        System.out.println(clazz);
        sqlSession.close();
    }


第二种方式:分步查询


代码


  • ClazzMapper接口


    /**
     * 分步查询。第一步:根据班级编号获取班级信息。
     * @param cid 班级编号
     * @return
     */
    Clazz selectByStep1(Integer cid);


  • ClazzMapper.xml


    <!--分步查询第一步:根据班级的cid获取班级信息。-->
    <resultMap id="clazzResultMapStep" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <collection property="stus"
                    select="com.powernode.mybatis.mapper.StudentMapper.selectByCidStep2"
                    column="cid" fetchType="eager" />
    </resultMap>
    <select id="selectByStep1" resultMap="clazzResultMapStep">
        select cid,cname from t_clazz where cid = #{cid}
    </select>


  • StudentMapper接口


    /**
     * 根据班级编号查询学生信息。
     * @param cid
     * @return
     */
    List<Student> selectByCidStep2(Integer cid);


  • StudentMapper.xml


    <select id="selectByCidStep2" resultType="Student">
        select * from t_stu where cid = #{cid}
    </select>


  • ClazzMapperTest


    @Test
    public void testSelectByStep1(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
        Clazz clazz = mapper.selectByStep1(1000);
        //System.out.println(clazz);
        // 只访问班级名字。
        System.out.println(clazz.getCname());
        // 只有用到的时候才会去执行第二步SQL
        //System.out.println(clazz.getStus());
        sqlSession.close();
    }


十四、MyBatis的缓存


fb82766863bce9bea5a97ef53aeb1c12.png


  • 缓存:cache


  • 缓存的作用:通过减少IO[读文件和写文件]的方式,来提高程序的执行效率。


。一方面是减少了IO。

。另一方面不再执行繁琐的查找算法。


  • mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库。


  • mybatis缓存包括:


范围:一级缓存小于二级缓存


。一级缓存:将查询到的数据存储到SqlSession中。【针对一次会话】


  • 一级缓存默认是开启的。不需要做任何配置。
  • 只要使用同一个SqlSession对象执行同一条SQL语句,就会走缓存。


。二级缓存:将查询到的数据存储到SqlSessionFactory中。【只针对整个数据库】


  • <setting name="cacheEnabled" value="true"> 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认就是true,无需设置。


  • 在需要使用二级缓存的SqlMapper.xml文件中添加配置:<cache />


  • 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口


  • SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中。此时二级缓存才可用。


  • 因此如果要使用二级缓存,一级缓存一定要失效或者集成其它第三方的缓存:比如EhCache【Java语言开发的】、Memcache【C语言开发的】等。


  • 第三方的缓存是为了代替mybatis自带的二级缓存。一级缓存是无法替代的。


  • 缓存只针对于DQL语句,也就是说缓存机制只对应select语句。


  • 常见的缓存技术


。字符串常量池

。整数型常量池线程池

。连接池


  • 思考:什么时候不走缓存?


。SqlSession对象不是同一个,肯定不走缓存。

。查询条件不一样,肯定也不走缓存。


  • 思考:什么时候一级缓存失效?


第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:


1.执行了sqlSession的clearCache()方法,这是手动清空缓存。

2.执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。


  • 二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。


14.1 一级缓存


  • 思考:什么时候不走缓存?


。SqlSession对象不是同一个,肯定不走缓存。

。查询条件不一样,肯定也不走缓存。


  • 思考:什么时候一级缓存失效?


。第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:

1.执行了sqlSession的clearCache()方法,这是手动清空缓存。

2.执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。


  • CarMapper接口


    /**
     * 根据id获取Car信息。
     * @param id
     * @return
     */
    Car selectById(Long id);


  • CarMapper.xml


    <select id="selectById" resultType="Car">
        select * from t_car where id = #{id}
    </select>
    <select id="selectById2" resultType="Car">
        select * from t_car where id = #{id}
    </select>


  • CarMapperTest


    // 思考:什么时候不走缓存?
    // SqlSession对象不是同一个,肯定不走缓存。
    // 查询条件不一样,肯定也不走缓存。
    // 思考:什么时候一级缓存失效?
    // 第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空:
    //     1. 执行了sqlSession的clearCache()方法,这是手动清空缓存。
    //     2. 执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存。
    @Test
    public void testSelectById(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper1 = sqlSession.getMapper(CarMapper.class);
        Car car1 = mapper1.selectById(164L);
        System.out.println(car1);
        // 手动清空一级缓存
        //sqlSession.clearCache();
        // 在这里执行了INSERT DELETE UPDATE中的任意一个语句。并且和表没有关系。
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        mapper.insertClazz(2000, "高三三班");
        CarMapper mapper2 = sqlSession.getMapper(CarMapper.class);
        Car car2 = mapper2.selectById(164L);
        System.out.println(car2);
        sqlSession.commit();
        sqlSession.close();
    }


    @Test
    public void testSelectById() throws Exception{
        // 如果要获取不同的SqlSession对象,不能使用以下代码。
        //SqlSession sqlSession = SqlSessionUtil.openSession();
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
        Car car1 = mapper1.selectById(164L);
        System.out.println(car1);
        CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
        Car car2 = mapper2.selectById(164L);
        System.out.println(car2);
        sqlSession1.close();
        sqlSession2.close();
    }


  • 测试结果:同一个Session


2ce8d4ba673686cd03cf0f5091bb5261.png


14.2 二级缓存


  • 二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】


  • 二级缓存相关的配置[了解]


1.eviction:指定从缓存中移除某个对象的淘汰算法。默认采用LRU策略。

a. LRU:Least Recently Used。最近最少使用。优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用。)

b. FIFO:First In First Out。一种先进先出的数据缓存器。先进入二级缓存的对象最先被淘汰。

c. SOFT:软引用。淘汰软引用指向的对象。具体算法和JVM的垃圾回收算法有关。

d. WEAK:弱引用。淘汰弱引用指向的对象。具体算法和JVM的垃圾回收算法有关。


2.flushInterval:

a. 二级缓存的刷新时间间隔。单位毫秒。如果没有设置。就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据。除非执行了增删改。


3.readOnly::


a. true:多条相同的sql语句执行之后返回的对象是共享的同一个。性能好。但是多线程并发可能会存在安全问题。

b. false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法。性能一般。但安全。


4.size:

a. 设置二级缓存中最多可存储的java对象数量。默认值1024。


  • mapper接口


/**
 * 测试二级缓存
 * @param id
 * @return
 */
Car selectById2(Long id);


  • mapper.xml


<!--
        默认情况下,二级缓存机制是开启的。
        只需要在对应的SqlMapper.xml文件中添加以下标签。用来表示“我”使用该二级缓存。
    -->
    <!--<cache/>-->
        <select id="selectById2" resultType="Car">
        select * from t_car where id = #{id}
    </select>


  • test


@Test
    public void testSelectById2() throws Exception{
        // 这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory。
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
        CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
        // 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)
        Car car1 = mapper1.selectById2(164L);
        System.out.println(car1);
        // 如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。
        // 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。
        sqlSession1.close();
        // 这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)
        Car car2 = mapper2.selectById2(164L);
        System.out.println(car2);
        // 程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
        //sqlSession1.close();
        // 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
        sqlSession2.close();
    }


14.3 MyBatis集成EhCache


按照以下步骤操作,就可以完成集成:


1.引入mybatis整合ehcache的依赖。


   <!--mybatis集成ehcache的组件-->
    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.2.2</version>
    </dependency>


2.在类的根路径下新建echcache.xml文件,并提供以下配置信息。


<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
    <diskStore path="e:/ehcache"/>
    <!--defaultCache:默认的管理策略-->
    <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
    <!--maxElementsInMemory:在内存中缓存的element的最大数目-->
    <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
    <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
    <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
    <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
    <!--FIFO:first in first out (先进先出)-->
    <!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
    <!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
    <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
</ehcache>


3.修改SqlMapper.xml文件中的<cache/>标签,添加type属性。


  <!--集成Ehcache组件-->
    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>


4.编写测试程序使用


@Test
    public void testSelectById2() throws Exception{
        // 这里只有一个SqlSessionFactory对象。二级缓存对应的就是SqlSessionFactory。
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
        CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
        // 这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。)
        Car car1 = mapper1.selectById2(164L);
        System.out.println(car1);
        // 如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。
        // 如果执行了这行代码,sqlSession1的一级缓存中的数据会放到二级缓存当中。
        sqlSession1.close();
        // 这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)
        Car car2 = mapper2.selectById2(164L);
        System.out.println(car2);
        // 程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
        //sqlSession1.close();
        // 程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
        sqlSession2.close();
    }


十五、MyBatis的逆向工程


  • 逆向工程:根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类等。
  • 思考:使用这个插件的话,需要给这个插件配置哪些信息?

。pojo类名、包名以及生成位置。

。SqlMapper.xml文件名以及生成位置。

。Mapper接口名以及生成位置。

。连接数据库的信息。

。指定哪些表参与逆向工程。

。…


15.1 逆向工程配置与生成


第一步:基础环境准备


  • 新建模块:mybatis-011-generator
  • 打包方式:jar


第二步:在pom中添加逆向工程插件


 <!--配置mybatis逆向工程的插件-->
    <!--定制构建过程-->
    <build>
        <!--可配置多个插件-->
        <plugins>
            <!--其中的一个插件:mybatis逆向工程插件-->
            <plugin>
                <!--插件的GAV坐标-->
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.1</version>
                <!--允许覆盖-->
                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
                <!--插件的依赖-->
                <dependencies>
                    <!--mysql驱动依赖-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.30</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>


第三步:配置generatorConfig.xml


  • 该文件名必须叫做:generatorConfig.xml


  • 该文件必须放在类的根路径下。


  • MyBatis3Simple版本


<?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>
    <!--
        targetRuntime有两个值:
            MyBatis3Simple:生成的是基础版,只有基本的增删改查。
            MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!--防止生成重复代码-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
        <commentGenerator>
            <!--是否去掉生成日期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/powernode"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- 生成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空白-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 生成SQL映射文件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成Mapper接口的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.powernode.mybatis.mapper"
                targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 表名和对应的实体类名-->
        <table tableName="t_car" domainObjectName="Car"/>
    </context>
</generatorConfiguration>


  • MyBatis3


<?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>
    <!--
        targetRuntime有两个值:
            MyBatis3Simple:生成的是基础版,只有基本的增删改查。
            MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <!--防止生成重复代码-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
        <commentGenerator>
            <!--是否去掉生成日期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/powernode"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- 生成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空白-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!-- 生成SQL映射文件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!-- 生成Mapper接口的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.powernode.mybatis.mapper"
                targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!-- 表名和对应的实体类名-->
        <table tableName="t_car" domainObjectName="Car"/>
    </context>
</generatorConfiguration>


第四步:运行插件


双击运行即可,会自动生成pojo类、mapper接口、mapper.xml文件


9e117d90cd691758949dfd1cdad3339c.png


15.2 测试逆向工程生成的是否好用


第一步:环境准备


  • 依赖:mybatis依赖、mysql驱动依赖、junit依赖、logback依赖
  • jdbc.properties
  • mybatis-config.xml
  • logback.xml


第二步:编写测试程序


  • MyBatis3Simple版本


package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
public class CarMapperTest {
    @Test
    public void testSelectAll(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }
    @Test
    public void testDeleteByPrimaryKey(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int count = mapper.deleteByPrimaryKey(164L);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
}


  • MyBatis3


。CarExample :封装查询条件

。按照条件进行查询

QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。

。封装条件,通过CarExample对象来封装查询条件


package com.powernode.mybatis.test;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.pojo.CarExample;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.List;
public class CarMapperTest {
    // CarExample类负责封装查询条件的。
    @Test
    public void testSelect(){
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 执行查询
        // 1. 查询一个
        Car car = mapper.selectByPrimaryKey(165L);
        System.out.println(car);
        // 2. 查询所有(selectByExample,根据条件查询,如果条件是null表示没有条件。)
        List<Car> cars = mapper.selectByExample(null);
        cars.forEach(car1 -> System.out.println(car1));
        System.out.println("=========================================");
        // 3. 按照条件进行查询
        // QBC 风格:Query By Criteria 一种查询方式,比较面向对象,看不到sql语句。
        // 封装条件,通过CarExample对象来封装查询条件
        CarExample carExample = new CarExample();
        // 调用carExample.createCriteria()方法来创建查询条件
        carExample.createCriteria()
                   .andBrandLike("帕萨特")
                   .andGuidePriceGreaterThan(new BigDecimal(20.0));
        // 添加or
        carExample.or().andCarTypeEqualTo("燃油车");
        // 执行查询
        List<Car> cars2 = mapper.selectByExample(carExample);
        cars2.forEach(car2 -> System.out.println(car2));
        sqlSession.close();
    }
}


十六、MyBatis使用PageHelper


16.1 limit分页


3871f0e42b488f48736ff1f9f31ea4a5.png


  • mysql的limit后面两个数字:


。第一个数字:startIndex(起始下标;下标从0开始;默认也为0)

。第二个数字:pageSize(每页显示的记录条数)


  • 假设已知页码pageNum,还有每页显示的记录条数pageSize,第一个数字可以动态的获取吗?


startIndex = (pageNum - 1) * pageSize


  • 所以,标准通用的mysql分页SQL:


select
  *
from
  tableName ......
limit
  (pageNum - 1) * pageSize, pageSize


  • 使用mybatis应该怎么做?


。mapper接口


/**
     * 分页查询
     * @param startIndex 起始下标。
     * @param pageSize 每页显示的记录条数
     * @return
     */
    List<Car> selectByPage(@Param("startIndex") int startIndex, @Param("pageSize") int pageSize);


  • mapper.xml


    <select id="selectByPage" resultType="Car">
        select * from t_car limit #{startIndex},#{pageSize}
    </select>


  • test


@Test
    public void testSelectByPage(){
        // 获取每页显示的记录条数
        int pageSize = 3;
        // 显示第几页:页码
        int pageNum = 3;
        // 计算开始下标
        int startIndex = (pageNum - 1) * pageSize;
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectByPage(startIndex, pageSize);
        cars.forEach(car -> System.out.println(car));
        sqlSession.close();
    }


获取数据不难,难的是获取分页相关的数据比较难。->插件


16.3 PageHelper插件


  • 使用PageHelper插件进行分页,更加的便捷。


  • 关键点:


。在查询语句之前开启分页功能


。在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展示。)


第一步:引入依赖


<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper</artifactId>
  <version>5.3.1</version>
</dependency>


第二步:在mybatis-config.xml文件中配置插件


typeAliases标签下面进行配置:


<plugins>
  <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>


第三步:编写Java代码


关键点:


  • 在查询语句之前开启分页功能。
  • 在查询语句之后封装PageInfo对象。(PageInfo对象将来会存储到request域当中。在页面上展示。)


代码


  • mapper接口


    /**
     * 查询所有的Car,通过分页查询插件PageHelper完成。
     * @return
     */
    List<Car> selectAll();


  • mapper.xml


<select id="selectAll" resultType="Car">
  select * from t_car
</select>


test


@Test
public void testSelectAll(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    // 一定一定一定要注意:在执行DQL语句之前。开启分页功能。
    int pageNum = 2;
    int pageSize = 3;
    PageHelper.startPage(pageNum, pageSize);
    List<Car> cars = mapper.selectAll();
    //cars.forEach(car -> System.out.println(car));
    // 封装分页信息对象new PageInfo()
    // PageInfo对象是PageHelper插件提供的,用来封装分页相关的信息的对象。
    PageInfo<Car> carPageInfo = new PageInfo<>(cars, 3);
    System.out.println(carPageInfo);
    sqlSession.close();
    /*
    PageInfo{pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=7, pages=3,
    list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=7, pages=3, reasonable=false, pageSizeZero=false}
    [Car{id=168, carNum='1204', brand='奥迪Q7', guidePrice=3.0, produceTime='2009-10-11', carType='燃油车'},
    Car{id=169, carNum='1205', brand='朗逸', guidePrice=4.0, produceTime='2001-10-11', carType='新能源'},
    Car{id=170, carNum='1206', brand='奔驰E300L', guidePrice=50.0, produceTime='2003-02-03', carType='新能源'}],
    prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true,
    navigatePages=3, navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
     */
}


十七、MyBatis的注解式开发


  • 注解式开发方式


。mybatis中也提供了注解式开发方式,采用注解可以减少Sql映射文件的配置。


。使用注解式开发的话,sql语句是写在java程序中的,这种方式也会给sql语句的维护带来成本。


官方:


使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。


。复杂的sql语句


23f9421f20000d8643f3a2cc15bc0129.png


。原则:简单sql可以注解。复杂sql使用xml。


17.1 @Insert


  • mapper接口


@Insert("insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
int insert(Car car);


  • test


@Test
public void testInsert(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = new Car(null,"6666","丰田霸道",32.0,"2020-11-11","燃油车");
    int count = mapper.insert(car);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}


17.2 @Delete


  • mapper接口


@Delete("delete from t_car where id = #{id}")
int deleteById(Long id);


  • test


@Test
public void testDeleteById(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    int count = mapper.deleteById(170L);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}


17.3 @Update


  • mapper接口


@Update("update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},car_type=#{carType} where id=#{id}")
int update(Car car);


  • test


@Test
public void testUpdate(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = new Car(165L,"6666","丰田霸道",32.0,"2020-11-11","燃油车");
    int count = mapper.update(car);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}


17.4 @Select


  • mapper接口


需要开启驼峰命名自动映射


@Select("select * from t_car where id = #{id}")
@Results({
        @Result(property = "id", column = "id"),
        @Result(property = "carNum", column = "car_num"),
        @Result(property = "brand", column = "brand"),
        @Result(property = "guidePrice", column = "guide_price"),
        @Result(property = "produceTime", column = "produce_time"),
        @Result(property = "carType", column = "car_type")
})
Car selectById(Long id);


  • test


@Test
public void testSelectById(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = mapper.selectById(171L);
    System.out.println(car);
    sqlSession.close();
}


目录
相关文章
|
5天前
|
SQL Java 数据库连接
MyBatis 框架入门理论与实践
MyBatis 框架入门理论与实践
33 6
|
5天前
|
Java
SSM框架整合
SSM框架整合
16 3
|
5天前
|
SQL XML Java
程序员都要懂的SQL防注入Mybatis框架SQL防注入
程序员都要懂的SQL防注入Mybatis框架SQL防注入
22 0
|
5天前
|
SQL 缓存 Java
持久层框架MyBatis
MyBatis是一款优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的操作。MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的POJOs(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
34 1
|
5天前
|
SQL 缓存 Java
【框架】MyBatis 框架重点解析
【框架】MyBatis 框架重点解析
10 0
|
5天前
|
SQL Java 数据库连接
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态SQL(下)
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态
6 0
|
5天前
|
SQL Java 数据库连接
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态SQL(上)
【JavaEE】懒人的福音-MyBatis框架—复杂的操作-动态SQL
5 0
|
5天前
|
SQL Java 数据库连接
【JavaEE】懒人的福音-MyBatis框架—[单表]增删改查等常规操作(下)
【JavaEE】懒人的福音-MyBatis框架—[单表]增删改查等常规操作
7 0
|
5天前
|
SQL 前端开发 Java
【JavaEE】懒人的福音-MyBatis框架—[单表]增删改查等常规操作(上)
【JavaEE】懒人的福音-MyBatis框架—[单表]增删改查等常规操作
8 0
|
5天前
|
Java 数据库连接 数据库
【JavaEE】懒人的福音-MyBatis框架—介绍、搭建环境以及初步感受
【JavaEE】懒人的福音-MyBatis框架—介绍、搭建环境以及初步感受
8 0