MyBatis(二)

164 阅读30分钟

MyBatis

技术体系

  1. 单一架构

    ​ 一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one

  2. 分布式架构

    ​ 一个项目,拆分称很多个模块,每个模块是一个工程。每个工程都是运行在自己的Tomcat上。模块之间可以互相调用。每一个模块内部可以堪称是一个单一架构的应用。

    SpringCloud离不开SpringBoot。每个微服务就是使用SpringBoot开发的。而各个微服务整体管理交给SpringCloud。

框架framework

  • 生活角度:

    框架就是毛坯房,半成品。

​ SSM就是免费的毛坯房,大浪淘沙,Java领域中最流行的框架。

​ 框架=框(限制 统一了风格)+架(支撑 保证质量、提高开发速度)

  • 技术角度:

    ​ 框架=jar包(特定问题的固定解决方案)+配置文件(个性化,可变,比如数据库的四个连接参数,SQL语句,访问路径)

    ​ 常用的基于Java EE的三大开源框架,已经从SSH、SSH2过度到了SSM:SpringMVC、Spring、MyBatis。

    ​ 总之,框架是一个半成品,已经对基础的代码进行了封装并提供响应的API,开发者在使用框架是直接调用封装好的API可以省去很多代码编写,从而提高工作效率和开发速度。

什么是MyBatis

​ MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。

  • MyBatis支持定制化SQL、存储过程以及高级映射
  • MyBatis避免了几乎所有的JDBC代码和手动设置参数以及结果集解析操作
  • MyBatis可以使用简单的XML或注解实现配置和原始映射;将接口和Java的POJO映射称数据库中的记录
  • MyBatis是一个半自动ORM框架。

什么是ORM

O:Object 对象

R:relational 关系

M:Mapping 映射

Java类----数据库表

Java类的成员变量---数据库表的字段

Java类的对象----数据库表的记录

MyBatis和Hibernate的不同

​ MyBatis将手写SQL语句的工作丢给开发者,可以更加精确的定义SQL,更加灵活,也便于优化性能。完成同样功能的两条SQL语句的性能可能相差十几倍到几十倍,在高并发、快相应要求的互联网系统中,对性能的影响更明显。

​ 总之,因为MyBatis具有封装少、映射多样化、支持存储过程、可以进行SQL语句优化等特点,符合互联网高并发、大数据、高性能高相应的要求,使它取代Hibernate成为了Java互联网中首选的持久框架。而对于性能要求不高的,比如内部管理系统、ERP等可以使用Hibernate。

使用MyBatis实现一个“HelloWorld”

  1. 创建数据库表(物理建模)

    CREATE DATABASE `mybatis-example`;
    
    USE `mybatis-example`;
    
    CREATE TABLE `t_emp`(
    emp_id INT AUTO_INCREMENT,
    emp_name CHAR(100),
    emp_salary DOUBLE(10,5),
    PRIMARY KEY(emp_id)
    );
    
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);
    INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jack",300.33);
    select * from t_emp
    
    
  2. 创建maven项目和实体类(逻辑建模)

    使用MyBatis是否必须是Web项目?

    不是的。只要是可以使用JDBC的地方都可以使用MyBatis

    使用Spring也不要求必须是Web项目

    但是使用SpringMVC必须是Web项目,它对Servlet进行了封装。

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Employee { //t_emp
        private Integer empId;//emp_id
        private String empName;//emp_name
        private Double empSalary;//emp_salary
    }
    
  3. 添加依赖(pom.xml)

    <dependencies>
    <!-- Junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
        <!-- Mybatis核心 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.3</version>
        </dependency>
    </dependencies>
    
    
  4. 创建配置文件(唯一的全局配置文件)

    习惯上命名为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="development">
            <!--某一开发环境 -->
            <environment id="development">
                <!--事务管理器,取值JDBC,使用是JDBC的事务 -->
                <transactionManager type="JDBC"/>
                <!--数据源:POOLED 带连接池的,提高获取连接的速度 -->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!-- 指定映射文件的位置:映射文件中进行ORM映射-->
            <!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
        </mappers>
    </configuration>
    
  5. 创建映射文件(会有多个,主要内容就是SQL语句)

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 命名空间,目前任意,后面使用Mapper代理时取值不任意-->
    <mapper namespace="a.b.c">
        <!--
            id:唯一
            resultType:返回值类型。如果返回值是集合,此处要想集合元素的类型(泛型) List<Employee>
         -->
        <select id="findAll" resultType="com.atguigu.entity.Employee">
            select * from t_emp
        </select>
    </mapper>
    
    
  6. 代码开发

    public class TestEmployee {
        @Test
        public void testFindAll() throws IOException {
            //1.根据配置文件创建了SqlSessionFactory
            //SqlSessionFactory factory = new DefaultSqlSessionFactory();
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
            //System.out.println(factory);
            //2.使用SqlSessionFactory创建了SqlSession
            SqlSession sqlSession = factory.openSession();
            //3.使用SqlSession对数据库进行操作并得到结果
            //仔细的体会一下:忽略了结果集处理的细节,直接返回List
            List<Employee> list = sqlSession.selectList("a.b.c.findAll");
            //4.处理结果
            list.forEach((emp)-> System.out.println(emp));
            //关闭资源
            sqlSession.close();
            //factory.close();        
        }
    }
    
    
  7. 运行测试

    问题1:java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for a.b.c.findAll

    原因1:写错了a.b.c.findAll namespace+id

    原因2:在配置config文件中没有指定映射(mapper)文件的位置

    <mappers>
        <!-- 指定映射文件的位置:映射文件中进行ORM映射-->
        <mapper resource="mappers/EmployeeMapper.xml"></mapper>
    </mappers>
    
    

    问题2:结果和记录条数相同,但是都是null

    原因:数据库表和实体类已经映射,但是数据库表的字段和实体类的成员变量没有映射,名称不同,还不能自动映射。

    <select id="findAll" resultType="com.atguigu.entity.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    </select>
    

    问题3:出现了JDK版本的不匹配。提示1.5和1.8不匹配

    原因:Maven项目创建的默认JDK版本是1.5

    解决方案1:修改idea的多个配置

    File——project structure——project——查看project SDK和Project language level是否是8

    File——Modules——选中当前模块——调整Language level

    File——setting——build,execution,deployment——Compiler——Java Compiler——查看module的bytecode version

    解决2:给Maven指定全局配置或者项目的局部配置

    <properties>
        <!--项目字符编码 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <!--源代码编译时JDK版本 -->
        <maven.compiler.source>8</maven.compiler.source>
        <!--运行时JDK版本 -->
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    

    问题4:MyBatis简单在哪?

    这是在搭建项目框架,很多操作只需要在此处做一次。搭建好框架后,其实的操作就是:

    在映射文件中写sql语句和在测试类调用sql语句。

    之前在JDBC中SQL语句写在代码中,现在SQL语句写在映射文件中,便于修改。

    //仔细的体会一下:忽略了结果集处理的细节,直接返回List List<Employee> list = sqlSession.selectList("a.b.c.findAll");

误区:SQL文件在xml文件,如果每次要执行该SQL语句时都去读取xml文件,效率太低。

解释:创建SqlSessionFactory时,会将配置文件和映射文件的信息都读到内存中,按照特定的结构来存在,不会每次都读硬盘。

"HelloWorld"的完善

1. 添加日志log4j

1)添加依赖

<!-- log4j日志 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2)准备配置文件,比如在resources目录下,名称固定log4j.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
        </layout>
    </appender>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>

3)观察效果(出现了SQL语句的DEBUG日志)

4)理解配置文件

a. Appender:目的地 日志写到哪里 ConsoleAppender 控制台 org.apache.log4j.FileAppender 写到文件

b. Layout:格式 日志写出什么样子 PatternLayout 指定格式 SimpleLayout HTMLLayout

c. Level:日志级别 FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

log4j曾经是最流行的日志框架,但是已经不再维护;现在建议使用log4j2和logback。

2. 提取连接参数属性文件

1)提取属性文件。要写在resources目录下,名称任意

wechat.dev.driver=com.mysql.jdbc.Driver
wechat.dev.url=jdbc:mysql://localhost:3306/mybatis-example
wechat.dev.username=root
wechat.dev.password=root

2)在配置文件中引入并使用属性文件

<!--读取指定的属性文件 -->
<properties resource="jdbc.properties"></properties>
<!--开发环境 -->
<environments default="development">
    <!--某一开发环境 -->
    <environment id="development">
        <!--事务管理器,取值JDBC,使用是JDBC的事务 -->
        <transactionManager type="JDBC"/>
        <!--数据源:POOLED 带连接池的,提高获取连接的速度 -->
        <dataSource type="POOLED">
            <property name="driver" value="${wechat.dev.driver}"/>
            <property name="url" value="${wechat.dev.url}"/>
            <property name="username" value="${wechat.dev.username}"/>
            <property name="password" value="${wechat.dev.password}"/>
        </dataSource>
    </environment>
</environments>

MyBatis单表开发1-无接口方式

1. findById

<select id="findById" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
</select>
@Test
public void testFindById() throws IOException {
    //1.根据配置文件创建了SqlSessionFactory
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    //2.使用SqlSessionFactory创建了SqlSession
    SqlSession sqlSession = factory.openSession();
    //3.使用SqlSession对数据库进行操作并得到结果
    //仔细的体会一下:忽略了结果集处理的细节,直接返回List
    Employee emp = sqlSession.selectOne("a.b.c.findById",2);
    //4.处理结果
    System.out.println(emp);
    //关闭资源
    sqlSession.close();
}

注意事项:

  1. 映射文件使用#{}来接收参数,参数名任意,但是建议见名知义
  2. 测试类中调用selectOne(),需要指定具体的参数

2. save

<!--insert update delete 没有resultType属性。有返回值,是int,代表DML操作影响了几条记录 -->
<insert id="insertEmp">
    insert into t_emp values(null,#{empName},#{empSalary})
</insert>

@Test
public void testInsertEmp() throws IOException {
    //1.根据配置文件创建了SqlSessionFactory
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    //2.使用SqlSessionFactory创建了SqlSession
    //方式1:自动提交事务,每个DML操作时一个单独的事务
    //SqlSession sqlSession = factory.openSession(true);
    //方式2:手动的提交事务
    SqlSession sqlSession = factory.openSession();//默认false
    //3.使用SqlSession对数据库进行操作并得到结果
    Employee emp = new Employee(null,"John",400.0);
    int n =  sqlSession.insert("a.b.c.insertEmp",emp);
    //手动提交或者回滚事务
    sqlSession.commit();
    //sqlSession.rollback();
    //4.处理结果
    System.out.println(n);
    //关闭资源
    sqlSession.close();
}

注意事项:DML操作需要提交事务

//方式1:自动提交事务,每个DML操作时一个单独的事务

SqlSession sqlSession = factory.openSession(true);

方式2:手动的提交事务 sqlSession.commit();

问题:There is no getter for property named 'empName1' in 'class com.atguigu.entity.Employee'

insert into t_emp values(null,#{empName},#{empSalary}) 底层调用的参数名称的getter方法 empName,其实调用的是getEmpName(),使用反射技术

如果没有getter方法,就会使用使用反射直接操作同名的成员变量

3. update

<update id="updateEmp">
    update t_emp set emp_name=#{empName},emp_salary=#{empSalary} where emp_id =#{empId}
</update>
@Test
public void testUpdateEmp() throws IOException {
    //1.根据配置文件创建了SqlSessionFactory
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    //2.使用SqlSessionFactory创建了SqlSession
    //方式1:自动提交事务,每个DML操作时一个单独的事务
    //SqlSession sqlSession = factory.openSession(true);
    //方式2:手动的提交事务
    SqlSession sqlSession = factory.openSession();//默认false
    //3.使用SqlSession对数据库进行操作并得到结果
    Employee emp = new Employee(6,"Johnson",500.0);
    int n =  sqlSession.update("a.b.c.updateEmp",emp);
    //手动提交或者回滚事务
    sqlSession.commit();
    //sqlSession.rollback();
    //4.处理结果
    System.out.println(n);
    //关闭资源
    sqlSession.close();
}

4. delete

<!--    <delete id="deleteEmp">-->
<!--        delete from t_emp where emp_id = #{abc}-->
<!--    </delete>-->
    <insert id="deleteEmp">
         delete from t_emp where emp_id = #{abc}
    </insert>

@Test
public void testDeleteEmp() throws IOException {
    //1.根据配置文件创建了SqlSessionFactory
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
    //2.使用SqlSessionFactory创建了SqlSession
    //方式1:自动提交事务,每个DML操作时一个单独的事务
    //SqlSession sqlSession = factory.openSession(true);
    //方式2:手动的提交事务
    SqlSession sqlSession = factory.openSession();//默认false
    //3.使用SqlSession对数据库进行操作并得到结果
   //int n =  sqlSession.delete("a.b.c.deleteEmp",6);
    int n =  sqlSession.update("a.b.c.deleteEmp",6);
    //手动提交或者回滚事务
    sqlSession.commit();
    //sqlSession.rollback();
    //4.处理结果
    System.out.println(n);
    //关闭资源
    sqlSession.close();
}

5.细节问题

  1. 映射文件中使用delete、update、insert标签没有区别,代码中调用SqlSession的insert、update、delete方法没有区别,关键是SQL语句

  2. 因为不管调用insert、update、delete哪个方法,底层调用的都是update(),更底层都是调用JDBC的executeUpdate()

  3. 不管查询是调用的是selectList(),还是selectOne(),底层调用的都是selectList.

  4. 目前已经学习了SqlSession的哪些方法

    (1) selectList

    (2) selectOne

    (3) insert

    (4) update

    (5) delete

    (6) Commit

  5. MyBatis的常用API

    (1) SqlSessionFactory

    (2) SqlSession: 它是Connection,还是Statement呢?都不是!!!!

MyBatis单表开发2-Mapper代理接口方式

前面已经使用MyBatis完成了对Emp表的CRUD操作,都是由SqlSession调用自身方法发送SQL命令并得到结果的,实现了MyBatis的入门。

但是却存在如下缺点:

1).不管是selectList()、selectOne()、selectMap(),都只能提供一个查询参数。如果要多个参数,需要封装到JavaBean中,并不一定永远是一个好办法。

2).返回值类型较为固定 。

3).只提供映射文件,没有提供数据库操作的接口,不利于后期的维护扩展,也和之前开发习惯不符。

在MyBatis中提供了另外一种成为Mapper代理(或称为接口绑定)的操作方式。在实际开发中也使用该方式。

Mybatis中的Mapper接口相当于以前的Dao。但是区别在于,Mapper仅仅是接口,我们不需要提供实现类。

1. 定义接口 相当于使用JDBC时的DAO接口,但是不需要写实现类

public interface EmployeeMapper {
    public List<Employee> findAll();
    public Employee findById(Integer empId);
    public int saveEmp(Employee emp);
    public int updateEmp(Employee emp);
    public int deleteEmp(Integer empId);

    public List<Employee> findEmp(String ename,String salary);
}

2. 映射文件

<!-- 命名空间,使用了Mapper代理后,namespace的取值,必须是Mapper接口的完整路径名称-->
<mapper namespace="com.atguigu.mapper.EmployeeMapper">
    <!-- id:唯一,必须和Mapper接口的方法名一致 -->
    <select id="findAll" resultType="com.atguigu.entity.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    </select>
    <insert id="saveEmp">
        insert into t_emp values(default,#{empName},#{empSalary})
    </insert>
</mapper>

命名空间namespace有要求:必须是Mapper接口的完整路径名称

具体操作的ID有要求:必须是接口的方法名

异常:org.apache.ibatis.binding.BindingException: Type interface com.atguigu.mapper.EmployeeMapper is not known to the MapperRegistry.

异常:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): com.atguigu.mapper.EmployeeMapper.findAll

3. 测试

public class TestEmployee {
    SqlSession sqlSession = null;
    @Before
    public void before() throws IOException {
        //1.根据配置文件创建了SqlSessionFactory
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        //2.使用SqlSessionFactory创建了SqlSession
        sqlSession = factory.openSession();//默认false
    }
    @After
    public void after(){
        //手动提交或者回滚事务
        sqlSession.commit();
        //关闭资源
        sqlSession.close();
    }
    @Test
    public void testFindAll(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        List<Employee> list = mapper.findAll();
        list.forEach((emp)-> System.out.println(emp));
    }
    @Test
    public void testInsertEmp(){
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee emp = new Employee(null,"bill gates",500.0);
        int n = mapper.saveEmp(emp);
        System.out.println(n);
    }
}

注意1: 因为MyBatis的CRUD操作流程固定,含有共同的代码,可以提取,并添加@Before、@After

注意2:使用该方式,第一步要通过SqlSession的getMapper()获取指定接口的动态生成的实现类对象

底层原理:底层使用了动态代理的设计模式实现了MyBatis的Mapper代理(接口绑定)

给SQL语句传参的两种方式

1. 使用#{} 底层使用PreparedStatement

<select id="findById" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_id = #{empId}
</select>

2. 使用${} 底层使用Statement

<select id="findById2" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_id = ${empId}
</select>

3. 使用${}和#{}实现模糊查询

#{}

<select id="findEmp" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_name like concat("%",#{ename},"%")
</select>

${}

<select id="findEmp2" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_name like  '%${ename}%'
</select>

结论:推荐使用#{},安全,不需要拼接字符串

数据输入

讲解的是方法的参数可以是一个还是多个,是基本类型还是复杂类型,在SQL语句中应该如何接收。肯定是使用#{}接收,但是具体如何来接收。

给实体类起别名,方便resultType使用

方法1:给每一个类起别名,别名任意

<typeAliases>
    <typeAlias type="com.atguigu.entity.Employee" alias="employee"></typeAlias>
    <typeAlias type="com.atguigu.entity.Dept" alias="dept"></typeAlias>
</typeAliases>

方法2:给一个包下的所有类起别名,别名是类名的首字母小写,其他字母不变

<typeAliases>
    <package name="com.atguigu.entity"/>
</typeAliases>

1. 单个简单参数(Integer、Double、String)

public Employee findById(Integer empId);
public List<Employee> findEmp(String empName);
<select id="findById" resultType="employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_id = #{empId}
</select>

<select id="findEmp" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_name like "%"#{ename}"%"
</select>

使用#{}来接收,参数名任意,建议见名知义。 经过测试发现对于模糊查询,#{}也可以使用 "%"#{ename}"%",要求是双引号。

2. 单个引用参数(Employee)

public int saveEmp(Employee emp);
<insert id="saveEmp">
    insert into t_emp values(default,#{empName},#{empSalary})
</insert>

底层调用的参数名称的getter方法 empName,其实调用的是getEmpName(),使用反射技术,如果没有getter方法,就会使用使用反射直接操作同名的成员变量

3. 单个引用参数(Map)

多个参数封装在map中传给mapper。

public List<Employee> findEmp2(Map map);
<select id="findEmp2" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_name like concat("%",#{ename11},"%") and emp_salary>=#{minSalary}
</select>
@Test
public void testFindEmp2(){
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    Map map = new HashMap<>();
    map.put("ename11","oh");
    map.put("minSalary",400);
    List<Employee> list = mapper.findEmp2(map);
    list.forEach((emp)-> System.out.println(emp));
}

#{key}使用Map的key来接收该key对应的value

4. 多个简单参数(Integer、Double、String)

public List<Employee> findEmp3(String ename,Double minSalary);

public List<Employee> findEmp4(@Param("ename") String ename,@Param("minSalary") Double minSalary);
<select id="findEmp3" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_name like concat("%",#{param1},"%") and emp_salary>=#{param2}
</select>

<select id="findEmp4" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_name like concat("%",#{ename},"%") and emp_salary>=#{minSalary}
</select>

如果没有使用@Param的话,参数只能是: parameters are [arg1, arg0, param1, param2]

推荐使用@Param方式,增加可读性。

5.多个引用参数(Employee,Dept)

 public int insertEmployee(@Param("emp") Employee employee,@Param("dep") Department department);
<insert id="insertEmployee">
        insert into t_emp(emp_id,emp_name,emp_salary,dep_id) values(null,#{emp.empName},#{emp.empSalary},#{dep.depId})
    </insert>

数据输出

1.基本数据类型

DML 返回一个int值,表示影响的行数

dao

int selectEmpCount();

mapper

<select id="selectEmpCount" resultType="int">
    select count(*) from t_emp
</select>

2.实体类型

返回值是一个实体类型,resultType就写该类的完整路径,也可以使用alias别名,别名在mybatis配置文件中设置。

dao

public Employee findById(Integer empId);

mapper

<select id="findEmp" resultType="com.atguigu.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
    where emp_name like "%"#{ename}"%"
</select>

3.List类型

返回值是一个List,resultType写List的泛型类型,也就是元素的类型 。

dao

 public List<Employee> findAll();

mapper

 <select id="findAll" resultType="com.atguigu.entity.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp
</select>

​ 此处使用了sql中的别名,如果pojo类和表中字段名不完全相同,那么需要我们手动对应【也就是对返回值字段重新命名】。同样的,resultType是用来对应表名的。

4.Map类型

​ 前提:返回值只有一行记录,可以有多个字段。它是使用字段名(别名)当作key,因此不能有多行数据,使用字段值作为value。

​ 但是对于findById方法来说则完全没有必要使用Map,而是使用对应的实体类,更加合理。

dao

Map<String,Object> selectEmpNameAndMaxSalary();

mapper

<select id="selectEmpNameAndMaxSalary" resultType="map">
     SELECT
        emp_name 员工姓名,
        emp_salary 员工工资,
        (SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
    FROM t_emp WHERE emp_salary=(
        SELECT MAX(emp_salary) FROM t_emp
    )
</select>

5.返回自增主键

多用于插入数据。

public int saveEmp4(Employee emp);

情景1:表主键支持自增。

mapper

<insert id="saveEmp" useGeneratedKeys="true" keyProperty="empId">
    insert into t_emp values (null,#{empName},#{empSalary})
</insert>

测试类

@Test
public void testSaveEmp3(){
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    Employee emp = new Employee(null,"washington",700.0);
    System.out.println(emp.getEmpId());
    int n = mapper.saveEmp4(emp);
    System.out.println(n);
    System.out.println(emp.getEmpId());
}

情景2:不管字段支不支持自增,都可以使用

mapper

<insert id="saveEmp">
    <selectKey order="AFTER" keyProperty="empId" resultType="int">
        select @@identity 
    </selectKey>
    insert into t_emp values (null,#{empName},#{empSalary})
</insert>

数据库表字段和实体属性的对应关系

1.自动映射

​ 如果数据库字段和实体类一致,会直接自动映射。

2.手动映射——起别名

​ 也就是使用sql对查询结果起别名。如果数据库字段和实体类不一致,就需要手动映射,可以给每个列起一个别名,别名就是实体类的属性名。

3.手动映射——驼峰命名

起别名方式的缺点:

​ 每个select都要起别名。

​ 条件:如果数据库的表字段和属性名不一致,但是有规律,比如字段是emp_id,而实体类属性是empId。那么就满足驼峰命名。

开启驼峰命名

在MyBatis-config.xml配置文件中配置。这个功能由mapUnderscoreToCamelCase控制。

<settings>
        <!-- 具体配置 -->
        <!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
        <!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
        <!-- 规则要求数据库表字段命名方式:单词_单词 -->
        <!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

在mybatis配置文件中,由于引入了dtd文档,所以元素是有顺序的,不能违反顺序。

4.手动映射——ResultMap

​ 如果数据库表字段和实体类不一致,但是也没有规律,那么可以使用别名来解决。但是每个select都要写一份,非常麻烦。此时就可以使用ResultMap,它可以定义一次,到处使用。

比如:

<resultMap id="employeeMap" type="com.dyy.Employee">
    <!--主键-->
	<id column="emp_id" property="empId"/>
    <!--非主键-->
    <result column="emp_name" property="empName"/>
    <result column="emp_salary" property="empSalary"/>
</resultMap>

多表查询

​ 在实际开发中,经常会将来自多张表的数据在一个位置显示。比如查询并显示的员工信息中会有来自部门表、岗位表的数据,而后台一般是定义一个方法: List findUser(conditions); 这就要求User中要包含部门Dept、岗位Position的信息。

1.如何实现多表查询

  1. 连接查询,使用MyBatis映射配置自动完成数据的组装,只需要执行一条SQL语句
  2. 分步查询,使用MyBatis映射配置自动完成数据的组装,需要执行多条SQL语句才能达到目的

2.关联关系【表之间的关系】

  1. 数量关系

    • 一对一
    • 一对多
    • 多对多
  2. 关联关系的方向

    主要实现在Java实体类中

    • 双向:双方都可以访问到对方
    • 单向:双方中只有一方能够访问到对方

3.多表查询

step1.创建数据库表

CREATE TABLE `t_customer` (
     `customer_id` INT NOT NULL AUTO_INCREMENT, 
     `customer_name` CHAR(100), 
     PRIMARY KEY (`customer_id`) 
);
CREATE TABLE `t_order` ( 
    `order_id` INT NOT NULL AUTO_INCREMENT, 
    `order_name` CHAR(100), 
    `customer_id` INT, 
    PRIMARY KEY (`order_id`) 
); 
INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');
INSERT INTO `t_customer` (`customer_name`) VALUES ('c02');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1'); 
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1'); 
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '2'); 

select * from t_customer
select * from t_order

step2.创建项目并创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Customer {
    private Integer customerId;
    private String customerName;
//对多
    private List<Order> orderList = new ArrayList<>();
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
    private Integer orderId;
    private String orderName;
    private Integer customerId;//知道所属客户的id,建议保留  insert
    //private String customerName;
    //对单
    private Customer customer;//知道所属客户的所有信息 select

}

数据库之间通过外键建立关联关系。

两个实体类之间通过属性(成员变量)来建立关联关系。

step3.搭建项目框架

  1. 实体类
  2. Mapper接口
  3. Mapper映射文件
  4. 测试类
  5. mybatis-config.xml(mybatis配置文件,指定了映射文件的位置)

step4.(多、一)对一

查询订单信息(包含客户信息)

(1)Mapper接口
public interface OrderMapper {
    /**
     * 查询指定编号的订单,返回该订单信息(其中携带客户信息)
     * @param orderId
     * @return
     */
    Order selectOrderWithCustomer(Integer orderId);
}
(2)Mapper映射文件
<select id="selectOrderWithCustomer"  resultMap="orderMap">
    select o.order_id,o.order_name,o.customer_id,c.customer_name
    from t_order o
    join t_customer c
    on (o.customer_id = c.customer_id)
    where order_id = #{orderId}
</select>

<resultMap id="orderMap" type="com.atguigu.entity.Order">
    <id column="order_id" property="orderId"></id>
    <result column="order_name" property="orderName"></result>
    <result column="customer_id" property="customerId"></result>
    <!--对一映射使用association -->
    <association property="customer" javaType="com.atguigu.entity.Customer">
        <id column="customer_id" property="customerId"></id>
        <result column="customer_name" property="customerName"></result>
    </association>
</resultMap>

如果只使用join而没有使用on指定条件的话,返回的是笛卡尔积,明显是不符合常理的。要避免此类错误。

(3)测试类
@Test
public void testSelectOrderWithCustomer(){
    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
    Order order = mapper.selectOrderWithCustomer(1);
    System.out.println(order);
    System.out.println(order.getCustomer());
}

step5.(多、一)对多

查询某客户的信息,包含可能多个订单信息。

(1)Mapper接口
public interface CustomerMapper {
    /**
     * 查询指定编号客户(携带订单信息,可能有多个订单)
     * @param customerId
     * @return
     */
    Customer selectCustomerWithOrderList(Integer customerId);
}

(2)Mapper映射文件
<select id="selectCustomerWithOrderList" resultMap="customerMap">
    select c.customer_id,c.customer_name,o.order_id,o.order_name
    from t_customer  c
    join t_order o
    on c.customer_id = o.customer_id
    where c.customer_id=#{customerId}
  </select>

<resultMap id="customerMap" type="com.atguigu.entity.Customer">
    <id column="customer_id" property="customerId"></id>
    <result column="customer_name" property="customerName"></result>
    <collection property="orderList" ofType="com.atguigu.entity.Order">
        <id column="order_id" property="orderId"></id>
        <result column="order_name" property="orderName"></result>
    </collection>
</resultMap>
(3)测试类
@Test
public void testSelectCustomerWithOrderList(){
    CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
    Customer customer = mapper.selectCustomerWithOrderList(1);
    System.out.println(customer);
    List<Order> orderList = customer.getOrderList();
    orderList.forEach(o-> System.out.println(o));
}

总结

多表查询的优点:

  1. 一条SQL查询多张表,结果包含多张表的数据
  2. 速度快

多表查询的缺点:

  1. 如果只想获取一张表的数据,但是多张表也会查询出来
  2. 对于一对多来说,这个问题更严重。只要客户名称,不要订单列表,却把订单列表(这是一个集合,多条记录)也查出来。对于多对一,这个问题还不太严重。要查询订单信息,同时把客户信息也查出来(就一个客户端,不是List)

针对这个缺点来说,如果确定要使用多表的数据,那就直接使用多表查询。

4.分步查询

​ 如果使用连接查询(join),只有立即加载,没有延迟加载(懒加载)。数据不管需不需要,都会被查询出来。

​ 如果不确定需不需要多表数据,可以采用更加灵活的延迟加载。要使用延迟加载,必须将连接查询(一条SQL)变成分步查询(多条SQL语句)

sql-1.查询指定编号的客户(不带订单的单表查询)

接口

public interface CustomerMapper {
   Customer findById(Integer customerId);
}

映射

<select id="findById" resultType="com.atguigu.entity.Customer">
    select * from t_customer where customer_id =#{customerId}
</select>

测试

@Test
public void testFindByCustomerId(){
    CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
    Customer customer = mapper.findById(1);
    System.out.println(customer);
}

sql2-查询指定编号的客户的订单(只有订单,单表)

接口

public interface OrderMapper {
    /**
     * 查询指定客户的订单,结果是List
     * @param customerId
     * @return
     */
    List<Order>  findByCustomerId(Integer customerId);
}

映射

<select id="findByCustomerId" resultType="com.atguigu.entity.Order">
    select * from t_order where customer_id=#{customerId}
</select>

测试

@Test
public void testFindOrderListByCustomerId(){
    OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
    List<Order> orderList = mapper.findByCustomerId(1);
    orderList.forEach(order-> System.out.println(order));
}

sql3-合并

<select id="findById" resultMap="customerMap">
    select * from t_customer where customer_id =#{customerId}
</select>
<resultMap id="customerMap" type="com.atguigu.entity.Customer">
    <id column="customer_id" property="customerId"></id>
    <result column="customer_name" property="customerName"></result>
    <collection property="orderList"
                select="com.atguigu.mapper.OrderMapper.findByCustomerId"
                column="customer_id">
    </collection>
</resultMap>

collection 关键属性select指明获取属性方法,column对应传入这个方法的参数。

5.延迟加载

什么是延迟加载

需要其他表信息的时候采取查询订单,不要订单信息的时候,就不查询。

如何实现延迟加载

  1. 第一步:延迟加载(lazyload 懒加载)全局开关lazyLoadingEnabled

    <!--mybatis配置文件-->
    <settings>
        <!-- 延迟加载的总开关-->
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
    
  2. 第二步:分开关:fetchType【eager饿汉式,lazy懒汉式】,默认是lazy的。

    <!--映射文件-->
    <resultMap id="customerMap2" type="com.atguigu.entity.Customer">
        <id column="customer_id" property="customerId"></id>
        <result column="customer_name" property="customerName"></result>
        <collection property="orderList"
                    select="com.atguigu.mapper.OrderMapper.findByCustomerId"
                    column="customer_id" 
                    fetchType="eager">
        </collection>
    </resultMap>
    

如果全局开关和分开关冲突,优先分开关的配置【就近】。

如何确定使用延迟加载还是立即加载

​ 连接(join)查询:一条SQL语句,效率高,不灵活,只有立即加载。

​ 分步查询:多条SQL语句,效率低。灵活,可以设置立即加载还是延迟(通过总开关和分开关)

​ 如果确定多张表的数据都需要,直接使用连接查询。

​ 如果不确定多张表数据是否都需要,可能有些场景需要,有些场景不需要,可以使用分步查询并设置延迟加载(懒加载)。

6.多对多关联和一对一关联

  1. 数据库中,不管一对一、一对多、多对多都是通过外键来实现的。外键关系其实是一种一对多的关系。

  2. 多对多的关联实现

    1. 数据库中使用外键解决:引入一个中间表。将一个多对多的关系,变成两个一对多的关系,中间表是多,之前两个表是一。

    2. 在MyBatis中如何实现多对多?

    • 方案1:可以像数据库表一样,定义三个实体类,在两个原生表和中间表之间建立一对多关系。

    • 方案2:还可以在Java类中直接建立多对多关系。以学生-选课表为例。

      Course类 增加属性 List<Student> stuList = new ArrayList()

      Student类 增加属性 List<Course> courseList = new ArrayList();

      映射文件中均使用Collection进行映射。

  3. 一对一关联实现

    1. 数据库中使用外键解决
      • 方案1:外键关联:将外键同时设置为unique,将一对多变成一对一。
      • 方案2:主键关联:同时是主键和外键。主键保证了唯一性和非空;外键保证了必须参考另一个类的主键。
    2. 在MyBatis中如何实现多对多?
      • 在java类中声明另一个表的对应类的对象作为属性。在两端的映射文件中都是用association。

动态SQL语句

有什么优势?

​ 在实际使用过程中,经常会使用到多条件查询,比如购物网站的搜索引擎,但是经常会有部分条件不取值。

​ 在JDBC中,是根据条件是否取值来进行sql语句的拼接,一般是使用StringBuilder类和它的append方法实现的,还需要编码,还是比较繁琐;在MyBatis中,动态SQL是在xml中配置的,便于修改,可以使sql更加灵活。

元素

if语句

<select id="findEmp" resultType="employee">
    select * from t_emp where 1=1
    <if test="empName!=null and empName!=''">
       and  emp_name like "%"#{empName}"%"
    </if>
    <if test="minSalary>0">
        and emp_salary >= #{minSalary}
    </if>
</select>

技巧:增加一个条件 1=1 ,后面的条件都不是第一个条件,都以and开始

where语句

<select id="findEmp" resultType="employee">
    select * from t_emp
    <where>
        <if test="empName!=null and empName!=''">
            and  emp_name like "%"#{empName}"%"
        </if>
        <if test="minSalary>0">
            and emp_salary >= #{minSalary}
        </if>
    </where>
</select>

注意:where会自动的去掉第一个条件的and、or

trim语句

<select id="findEmp" resultType="employee">
    select * from t_emp
    <trim prefix="where" prefixOverrides="and">
        <if test="empName!=null and empName!=''">
            and  emp_name like "%"#{empName}"%"
        </if>
        <if test="minSalary>0">
            and emp_salary >= #{minSalary}
        </if>
    </trim>
</select>

prefix属性:指定要动态添加的前缀

suffix属性:指定要动态添加的后缀

prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值

suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值

choose/when/otherwise标签(switch case)

<select id="findEmp" resultType="employee">
    select * from t_emp where
    <choose>
        <when test="empName!=null and empName!=''">
           emp_name like "%"#{empName}"%"
         </when>
        <when test="minSalary>0">
             emp_salary >= #{minSalary}
        </when>
        <otherwise>
            1=1
        </otherwise>
    </choose>
</select>

不管有多少个条件满足,只执行第一个满足条件的。如果一个条件也没有,就执行otherwise,otherwise不要少。

foreach

public List<Employee> findEmp5(@Param("idArr") int [] idArr);

public List<Employee> findEmp6(@Param("idList")List<Integer> idList);
<!-- Available parameters are [array, arg0]-->
<!-- Available parameters are [idArr, param1]-->
<select id="findEmp5" resultType="employee">
    select * from t_emp where emp_id in
    <foreach collection="idArr" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>
<!-- Parameter 'array' not found. Available parameters are [arg0, collection, list] -->
<select id="findEmp6" resultType="employee">
    select * from t_emp where emp_id in
    <foreach collection="idList" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>
@Test
public void testFindEmp2(){
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    //int [] idArr = {1,7,10,20};
    //List<Employee> list = mapper.findEmp5(idArr);
    List<Integer> idList = new ArrayList<>();
    Collections.addAll(idList,1,7,10);
    List<Employee> list = mapper.findEmp6(idList);
    list.forEach(emp-> System.out.println(emp));
}

注意事项

  1. 如果参数是数组,没有使用@Param,可以使用array来接收
  2. 如果参数是List,没有使用@Param,可以使用collection、list来接收

set

针对update操作,会自动的去掉多余的,

public int updateEmp(Employee emp);
<update id="updateEmp">
    update t_emp
    <set>
       <if test="empName!=null and empName!=''">
           emp_name = #{empName},
       </if>
        <if test="empSalary>0">
            emp_salary = #{empSalary}
        </if>
    </set>
    where emp_id = #{empId}
</update>

sql

提取SQL语句的公共部分,并使用include标签来引用。便于修改。

<sql id="empColumns">
    emp_id,emp_name,emp_salary
</sql>
<select id="findAll" resultType="Employee">
select <include refid="empColumns"></include> from t_emp where 1=1
</select>

缓存

1. 作用

如果缓存中有需要的数据,就直接读取缓存中数据,而不去读取硬盘。减少对硬盘(数据库)的访问次数,提高了效率。

2.MyBatis缓存分类

一级缓存:SqlSession级别 默认开启

二级缓存:SqlSessionFactory级别 默认没有开启

3. MyBatis缓存访问步骤

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  2. 如果二级缓存没有命中,再查询一级缓存
  3. 如果一级缓存也没有命中,则查询数据库
  4. 从数据库查询的数据,会直接放入到一级缓存
  5. SqlSession关闭之前,一级缓存中的数据会写入二级缓存

4.一级缓存

@Test
public void testCache1(){
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    Employee emp = mapper.findById(1);
    System.out.println(emp);
    //清空一级缓存
    sqlSession.clearCache();
//提交事务,也会清空一级缓存
//sqlSession.commit();
    Employee emp2 = mapper.findById(1);
    System.out.println(emp2);
    Employee emp3 = mapper.findById(2);
    System.out.println(emp3);
}

什么时候一级缓存会失效?

  1. 不是同一个SqlSession
  2. 同一个SqlSession但是查询条件发生了变化
  3. 同一个SqlSession两次查询期间手动清空了缓存
  4. 同一个SqlSession两次查询期间执行了任何一次增删改操作
  5. 同一个SqlSession两次查询期间提交了事务

5.二级缓存

(1)全局开关:默认开启

<settings>
    <!-- 二级缓存总开关,默认就是true-->
    <setting name="cacheEnabled" value="true"/>
 </settings>

(2)局部开关:二级缓存以namespace为单位

<mapper namespace="com.atguigu.mapper.EmployeeMapper">
    <!--二级缓存的分开关,以namespace为单位 -->
    <cache/>
    <select id="findById" resultType="employee">
        select * from t_emp where emp_id = #{empId}
    </select>
</mapper>

二级缓存分开关的设置:

<cache type="" size="" blocking="" eviction="" flushInterval="" readOnly=""/>
  1. Type:指定具体的二级缓存类型。如果不指定,就使用MyBatis自带的二级缓存。
  2. Size:代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  3. flushInterval:刷新(清空缓存)间隔。默认情况是不设置,也就是没有刷新间隔
  4. readOnly:只读。True:性能高 false,性能低,安全性高
  5. Eviction:删除。比如二级缓存中最多放100个缓存内容,第101来了,怎么办?
  6. LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
  7. FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
  8. Blocking 阻塞:访问某个缓存数据,在访问过程中对key(sql语句)加锁,其他的请求同一个SQL语句,要等待。保证了只有一个请求来访问缓存中的一份数据。

(3)实体类要序列化

​ 什么时候需要序列化:在在内存中存储不需要序列化,如果内存的数据要存入硬盘,或者在网络上传输,就要序列化(其实是变成字节数组)。

​ 一级缓存肯定在内存中,二级缓存可能在内存中,但是二级缓存数据多,也可以存储到外存中,所以存在二级缓存中的数据必须序列化。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable { //t_emp
    private Integer empId;//emp_id
    private String empName;//emp_name
    private Double empSalary;//emp_salary
}

(4)测试

@Test
public void testCache2(){
    //由一个工厂创建两个SqlSession
    SqlSession sqlSession1 = factory.openSession();
    SqlSession sqlSession2 = factory.openSession();
    EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
    EmployeeMapper mapper2 = sqlSession2.getMapper(EmployeeMapper.class);

    //使用两个SqlSession发送同一个请求
    Employee emp1 = mapper1.findById(1);
    System.out.println(emp1);
    //!!! 关闭SqlSession,将该SqlSession一级缓存中数据存入二级缓存
   // sqlSession1.close();

    Employee emp2 = mapper2.findById(1);
    System.out.println(emp2);
}

整合Ehcache

​ Ehcache是一种开源的、基于标准的缓存,它可以提高性能、减轻数据库负担并简化可伸缩性。它是使用最广泛的基于Java的缓存,因为它健壮、可靠、功能齐全,并与其他流行的库和框架集成。Ehcache可以从进程内缓存扩展到进程内/进程外混合部署和TB大小的缓存。

1.添加依赖

<!-- Mybatis EHCache整合包 -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

2.配置文件

在resources目录下创建ehcache.xml【必须是这个名字,否则是识别不到的】

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="D:\atguigu\ehcache"/>

    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

3.加入log4j适配器依赖

存在SLF4J时,是无法直接访问log4j的,需要加入一个适配器类:slf4j-log4j12。

<!-- SLF4j和log4j的适配器类 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.30</version>
    <scope>test</scope>
</dependency>

4.指明二级缓存使用Ehcache

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

日志框架

mybatis-1.png

日志框架类型

门面

名称说明
JCL陈旧
SLFJ适合
jboss-logging特殊专业领域使用

实现

名称说明
log4j最初版
JUL(java.util.logging)JDK自带
log4j2Apache收购log4j后全面重构,内部实现和log4j完全不同
logback优雅、强大

logback使用方式

  1. 添加依赖
<!-- slf4j日志门面的一个具体实现:logback -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>
  1. 添加配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="true">
        <!-- 指定日志输出的位置 -->
        <appender name="STDOUT"
                  class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <!-- 日志输出的格式 -->
                <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
                <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern>
            </encoder>
        </appender>
        <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
        <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
        <root level="DEBUG">
            <!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
            <appender-ref ref="STDOUT" />
        </root>
        <!-- 根据特殊需求指定局部日志级别 -->
        <logger name="com.atguigu.crowd.mapper" level="INFO"/>
    </configuration>
    

缓存源码

Cache相关的API

缓存(一级和二级缓存)的顶级接口:org.apache.ibatis.cache.Cache

缓存的实现类:org.apache.ibatis.cache.impl.PerpetualCache

缓存的(第三方的)实现类:org.mybatis.caches.ehcache.EhcacheCache

缓存的其他实现类:(装饰类)FifoCache,LruCache,SoftCache,WeakCache

装饰模式

蛋糕接口:(Cache)

蛋糕实现类:奶油蛋糕 冰激凌蛋糕(PerpetualCache EhcacheCache)

蛋糕的装饰:卡片、干果、蜡烛(FifoCache、LruCache是添加了装饰的蛋糕)

如果采用继承,就会数不清的实现类。采用装饰模式,可以减少子类的数量,是继承的一种替代方案。现场组装。

蛋糕接口:(InputStream)

蛋糕实现类:奶油蛋糕 冰激凌蛋糕(FileInputStream ByteArrayInputStream)

蛋糕的装饰:卡片、干果、蜡烛(BufferedInputStream DataInputStream)

InputStream is = new FileInputStream(“readme.txt”);

BufferedInputStream bis = new BufferedInputStream(is );

DataInputStream dis = new DataInputStream(bis);

 

DataInputStream dis = 

new DataInputStream(new BufferedInputStream( new FileInputStream(“readme.txt”)));

Cache内部有一个Map,存储缓存的信息

所有的装饰类中必须有一个Cache的引用,说明对谁进行装饰。

org.apache.ibatis.cache.impl.PerpetualCache是Mybatis的默认缓存,也是Cache接口的默认实现。Mybatis一级缓存和自带的二级缓存都是通过PerpetualCache来操作缓存数据的。但是这就奇怪了,同样是PerpetualCache这个类,怎么能区分出来两种不同级别的缓存呢

其实很简单,调用者不同。

· 一级缓存:由BaseExecutor调用PerpetualCache

· 二级缓存:由CachingExecutor调用PerpetualCache,而CachingExecutor可以看做是对BaseExecutor的装饰

2.一级Cache的源码

mybatis-2.png

mybatis-3.png

3. 二级Cache的源码

使用默认的二级Cache:PerpetualCache

mybatis-4.png

使用第三方的EhCacheCache

mybatis-5.png

源码:

mybatis-6.png MyBatis的缓存查询过程:

先查询二级缓存,

二级缓存中没有,再查询一级缓存。

一级缓存中没有,就查询数据库

从数据库中查询到数据,放入到一级缓存。

逆向工程

正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。

逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:实体类、Mapper接口、映射文件

使用

1.添加插件

<!-- 控制Maven在构建过程中相关配置 -->
<build>
    <!-- 构建过程中用到的插件 -->
    <plugins>
        <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
        <plugin>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.0</version>
            <!-- 插件的依赖 -->
            <dependencies>
                <!-- 逆向工程的核心依赖 -->
                <dependency>
                    <groupId>org.mybatis.generator</groupId>
                    <artifactId>mybatis-generator-core</artifactId>
                    <version>1.3.2</version>
                </dependency>
                <!-- 数据库连接池 -->
                <dependency>
                    <groupId>com.mchange</groupId>
                    <artifactId>c3p0</artifactId>
                    <version>0.9.2</version>
                </dependency>
                <!-- MySQL驱动 -->
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.8</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>

2.添加配置文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--
            targetRuntime: 执行生成的逆向工程的版本
                    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
                    MyBatis3: 生成带条件的CRUD(奢华尊享版)
     -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis-example"
                        userId="root"
                        password="root">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.atguigu.entity" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="mappers"  targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mapper"  targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Employee"/>
        <table tableName="t_customer" domainObjectName="Customer"/>
        <table tableName="t_order" domainObjectName="Order"/>
    </context>
</generatorConfiguration>

3. 开始逆向

mybatis-7.png

QBC(了解)

QBC:Query By Criteria

QBC查询最大的特点就是将SQL语句中的WHERE子句进行了组件化的封装,让我们可以通过调用Criteria对象的方法自由的拼装查询条件。

Mapper映射

package向mybatis-config.xml引入mapper映射文件

<mappers>
    <!-- 指定映射文件的位置:映射文件中进行ORM映射-->
   <package name="com.atguigu.mapper"/>
</mappers>

前提

  1. Mapper接口和Mapper配置文件名称一致
  2. Mapper配置文件放在Mapper接口所在的包内

插件机制(了解)

​ 插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。著名的Mybatis插件包括 PageHelper(分页插件)、通用 Mapper(SQL生成插件)等。

​ 如果想编写自己的Mybatis插件可以通过实现org.apache.ibatis.plugin.Interceptor接口来完成,表示对Mybatis常规操作进行拦截,加入自定义逻辑

​ 但是由于插件涉及到Mybatis底层工作机制,在没有足够把握时不要轻易尝试。

类型处理器typeHandler(了解)

1、Mybatis内置类型处理器

mybatis-8.png

2、Mybatis自定义类型处理器步骤

1.自定义类型转换器

public class AddressTypeHandler extends BaseTypeHandler<Address> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Address parameter, JdbcType jdbcType) throws SQLException {

    }

    @Override
    public Address getNullableResult(ResultSet rs, String columnName) throws SQLException {
        //获取当前列的数据
       String str =  rs.getString(columnName);//"河北省,张家口市,崇礼县"
        //处理该列的数据转换为Address
        String[] arr = str.split(",");
        Address address = new Address(arr[0],arr[1],arr[2]);
        //返回Address
        return address;
    }

    @Override
    public Address getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return null;
    }

    @Override
    public Address getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return null;
    }
}

2.在配置文件中进行注册

<typeHandlers>
    <typeHandler handler="com.atguigu.typehandler.AddressTypeHandler"
                 javaType="com.atguigu.entity.Address"
                 jdbcType="VARCHAR"></typeHandler>
</typeHandlers>

3.修改实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
    private String province;
    private String city;
    private String county;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee implements Serializable { //t_emp
    private Integer empId;//emp_id
    private String empName;//emp_name
    private Double empSalary;//emp_salary
    //private String empAddress;
    private Address empAddress;
}

MyBatis底层对JDBC的封装(重要)

目前为止,还没有看见过Connection、Statement、ResultSet。

问题1:SqlSessionFactory就相当于DriverMangaer,SqlSession就相当于Connection,Mapper就相当于Statement??

这个说法完全错误。

SqlSession是一个接口,这里返回的是其实现类DefaultSqlSession的一个实例。其中有一个Executor类型的成员变量。其实进行数据库操作时SqlSession就是一个门面,真正干活的却是Executor(BaseExecutor和CacheingExecutor)。

1. Mybatis底层四大对象:

四大对象:SqlSessionFactory SqlSession Mapper ?? 错了,都错了

四大对象:Executor、StatementHandler、ParameterHandler、ResultSetHandler

①Executor

执行器,由它来调度StatementHandler、并由StatementHandler来调度ParameterHandler、ResultSetHandler等来执行对应的SQL。

②StatementHandler

使用数据库的Statemet(PreparedStatement)执行操作

③ParameterHandler

处理SQL参数;比如查询的where条件、和insert、update的字段值的占位符绑定。

④ResultSetHandler

处理结果集ResultSet的封装返回。将数据库字段的值赋给实体类对象的成员变量。

2. Mybatis底层四大步骤:

SimpleExecutor中

doQuery() select ------------>JDBC executeQuery()

doUpdate() insert update delete--------->jDBC executeUpdate()

核心步骤就是四步:

第一步:获取数据库连接。

第二步:调用StatementHandler的prepare()方法,创建Statement对象并设置其超时、获取的最大行数等。

第三步:调用StatementHandler的parameterize()方法。负责将具体的参数传递给SQL语句。

第四步:调用StatementHandler的query()/update方法。完成查询/DML操作,并对结果集进行封装处理,然后返回最终结果。

第一步:获取数据库连接 Connection connection = getConnection(statementLog);

第二步:创建Statement(PreparedStatement) Statement stmt = handler.prepare(connection, transaction.getTimeout());

第三步:处理参数 handler.parameterize(stmt);

第四步:执行CRUD操作 return handler.update(stmt);// insert update delete 返回 int return handler.query(stmt, resultHandler) ;s //select 返回List 底层会用ResultSetHandler

mybatis-9.png