Mybatis学习笔记

Mybatis

第一章 概述

1.1 什么是mybatis?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1.2 为什么需要mybaits?

主要就是简化了jdbc操作,能够更方便的操作数据库。

1.3 Mybatis的特点

简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
提供映射标签,支持对象与数据库的orm字段关系映射
提供对象关系映射标签,支持对象关系组建维护
提供xml标签,支持编写动态sql。

第二章 环境搭建

这里以查询员工表信息为例子

a>引入依赖

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

b>创建实体类

public class Employee {
   
   private Integer id;
   private String lastName;
   private String email;
   private String gender;

   public Integer getId() {
      return id;
   }

   public void setId(Integer id) {
      this.id = id;
   }

   public String getLastName() {
      return lastName;
   }

   public void setLastName(String lastName) {
      this.lastName = lastName;
   }

   public String getEmail() {
      return email;
   }

   public void setEmail(String email) {
      this.email = email;
   }

   public String getGender() {
      return gender;
   }

   public void setGender(String gender) {
      this.gender = gender;
   }

   @Override
   public String toString() {
      return "Employee{" +
            "id=" + id +
            ", lastName='" + lastName + '\'' +
            ", email='" + email + '\'' +
            ", gender='" + gender + '\'' +
            '}';
   }
}

c>创建全局配置文件mybatis-config

<?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">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=CTT" />
                <property name="username" value="root" />
                <property name="password" value="199787" />
            </dataSource>
        </environment>
    </environments>
    <!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
    <mappers>
        <mapper resource="EmployeeMapper.xml" />
    </mappers>
</configuration>

d>编写sql映射文件EmployeeMapper.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="abc">



    <select id="getEmpById" resultType="com.qy.mybatis.Employee">
      select id,last_name lastName,email,gender from employee where id = #{id}
   </select>

</mapper>

e>测试

import com.qy.mybatis.Employee;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class test {


    /**
     * 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源一些运行环境信息
     * 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。
     * 3、将sql映射文件注册在全局配置文件中
     * 4、写代码:
     *        1)、根据全局配置文件得到SqlSessionFactory;
     *        2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
     *           一个sqlSession就是代表和数据库的一次会话,用完关闭
     *        3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
     *
     * @throws IOException
     */

    public SqlSessionFactory getSqlSessionFactory() throws IOException, IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        return new SqlSessionFactoryBuilder().build(inputStream);
    }


    @Test
    public void test1() throws IOException {

        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try {
            Employee employee = openSession.selectOne(
                    "abc.getEmpById", 1);
            System.out.println(employee);
        } finally {
            openSession.close();
        }

    }





}

以上方法,我们在调用的使用,需要加上命名空间+sql唯一标注很不方便。我们可以才有接口式编程

a>创建一个Dao接口

public interface EmployeeDao  {

    Employee getEmpById(Integer id);
}

b>sql映射文件的命名空间指向这个接口并且sql的id和方法名保持一致

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qy.mybatis.dao.EmployeeDao">



    <select id="getEmpById" resultType="com.qy.mybatis.entity.Employee">
      select id,last_name lastName,email,gender from employee where id = #{id}
   </select>

</mapper>

c>测试

@Test
public void test2() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);//这里是获取的一个代理对象
    Employee employee= employeeDao.getEmpById(1);
    System.out.println(employee);
}

小结:

1.接口式编程
原生: Dao ====> DaoImpl
mybatis: Mapper ====> xxMapper.xml
2.SqlSession代表和数据库的一次会话;用完必须关闭;
3.SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。
4.mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。
(将接口和xml进行绑定)EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
5.两个重要的配置文件:
mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等…系统运行环境信息
sql映射文件:保存了每一个sql语句的映射信息:将sql抽取出来。

第三章 Mybatis全局配置文件

  • configuration 配置
    • properties 属性
    • settings 设置
    • typeAliases 类型命名
    • typeHandlers 类型处理器
    • objectFactory 对象工厂
    • plugins 插件
    • environments 环境
      • environment 环境变量
        • transactionManager 事务管理器
        • dataSource 数据源
    • databaseIdProvider 数据库厂商标识
    • mappers 映射器

3.1 通过Properties引入外部配置文件

<!--
1、mybatis可以使用properties来引入外部properties配置文件的内容;
resource:引入类路径下的资源
url:引入网络路径或者磁盘路径下的资源
 -->
  <properties resource="dbConfig.properties"></properties>

3.2 setting标签配置

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3.3 typeAliases-别名

<configuration>
	...
	<typeAliases>
		<typeAlias alias="Author" type="domain.blog.Author"/>
		<typeAlias alias="Blog" type="domain.blog.Blog"/>
		<typeAlias alias="Comment" type="domain.blog.Comment"/>
		<typeAlias alias="Post" type="domain.blog.Post"/>
		<typeAlias alias="Section" type="domain.blog.Section"/>
		<typeAlias alias="Tag" type="domain.blog.Tag"/>
	</typeAliases>
</configuration>

3.4 typeHandlers-类型处理器

无论是 MyBatis 在预处理语句( PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-klVEXZS6-1650933511429)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220217204655780.png)]

3.5 plugins-插件简介

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。

了解mybatis运行原理才能更好开发插件。

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)

3.6 enviroments-运行环境

  • MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
  • 每种环境使用一个environment标签进行配置并指定唯一标识符
  • 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境

3.7 databaseIdProvider-多数据库支持

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
  <property name="MySQL" value="mysql" />
</databaseIdProvider>

DB_VENDOR - 会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短

databaseId属性在映射xml使用

<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
	databaseId="mysql">
	select * from employee where id = #{id}
</select>
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
	databaseId="oracle">
	select e.* from employee e where id = #{id}
</select>

  • 通过<environments default="mysql">切换数据库,便能切换SQL

3.8 sql映射注册

。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。例如:

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名 -->
<!--
注册接口
class:引用(注册)接口,
	1、有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
	2、没有sql映射文件,所有的sql都是利用注解写在接口上;
	推荐:
		比较重要的,复杂的Dao接口我们来写sql映射文件
		不重要,简单的Dao接口为了开发快速可以使用注解;
-->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

第四章 映射文件-增删改查

映射文件指导着MyBatis如何进行数据库增删改查,有着非常重要的意义;

cache –命名空间的二级缓存配置
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 自定义结果集映射
parameterMap – 已废弃!老式风格的参数映射
sql –抽取可重用语句块。
insert – 映射插入语句
update – 映射更新语句
delete – 映射删除语句
select – 映射查询语句

4.1 增删改查

1.dao接口编写

public interface EmployeeDao  {

    Employee getEmpById(Integer id);

    Integer addEmp(Employee employee);

    Integer updateEmp(Employee employee);

    Integer deleteEmp(Integer id);
}

2.mapper映射文件编写

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qy.mybatis.dao.EmployeeDao">
	
    <select id="getEmpById" resultType="com.qy.mybatis.entity.Employee">
		select id,last_name ,email,gender from employee where id = #{id}
	</select>

	<insert id="addEmp" parameterType="com.qy.mybatis.entity.Employee">
	    insert into employee(last_name,email,gender)
		values(#{lastName},#{email},#{gender})
	</insert>

	<update id="updateEmp" parameterType="com.qy.mybatis.entity.Employee">
		update employee
		set
		last_name = #{lastName}
		where
		id = #{id}
	</update>

	<delete id="deleteEmp" parameterType="com.qy.mybatis.entity.Employee">
		delete  from employee
		where
		id = #{id}
	</delete>

</mapper>


3.测试

  @Test
    public void test3() throws IOException {
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);
        Employee addEmployee =  new Employee();
        addEmployee.setLastName("小王");
        addEmployee.setGender("0");
        addEmployee.setEmail("xiaowang@qq.com");
        Integer integer = employeeDao.addEmp(addEmployee);
        System.out.println(integer);

        Employee updateEmployee = new Employee();
        updateEmployee.setId(2);
        updateEmployee.setLastName("qianyue");
        updateEmployee.setEmail("qianyue@qq.com");
        updateEmployee.setGender("0");
        employeeDao.updateEmp(updateEmployee);


        employeeDao.deleteEmp(1);

        openSession.commit();




    }

注意:

mybatis允许增删改直接定义以下类型返回值
Integer、Long、Boolean、void
我们需要手动提交数据
sqlSessionFactory.openSession();=》手动提交
sqlSessionFactory.openSession(true);
=》自动提交

4.2 获取自增主键的值

获取自增主键的值:

1.mysql

mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
useGeneratedKeys=“true”;使用自增主键获取主键值策略
keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
代码演示

	<insert id="addEmp" parameterType="com.qy.mybatis.entity.Employee" useGeneratedKeys="true" 			         keyProperty="id">
	    insert into employee(last_name,email,gender)
		values(#{lastName},#{email},#{gender})
	</insert>

这样我们可以通过插入的这个类对象获取主键值

2.Oracle

  • Oracle不支持自增;Oracle使用序列来模拟自增;
  • 每次插入的数据的主键是从序列中拿到的值;如何获取到这个值;
#从序列获取新主键值
select employee_seq.nextval from dual;

oracle 通过selectKey 获取序列值

<insert id="addEmp" databaseId="oracle">
	<!-- 
	keyProperty:查出的主键值封装给javaBean的哪个属性
	order="BEFORE":当前sql在插入sql之前运行
		   AFTER:当前sql在插入sql之后运行
	resultType:查出的数据的返回值类型
	
	BEFORE运行顺序:
		先运行selectKey查询id的sql;查出id值封装给javaBean的id属性
		在运行插入的sql;就可以取出id属性对应的值
	AFTER运行顺序:
		先运行插入的sql(从序列中取出新值作为id);
		再运行selectKey查询id的sql;
	 -->
	<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
		<!-- 编写查询主键的sql语句 -->
		<!-- BEFORE-->
		select EMPLOYEES_SEQ.nextval from dual 
		<!-- AFTER:
		 select EMPLOYEES_SEQ.currval from dual -->
	</selectKey>
	
	<!-- 插入时的主键是从序列中拿到的 -->
	<!-- BEFORE:-->
	insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) 
	values(#{id},#{lastName},#{email<!-- ,jdbcType=NULL -->}) 

	<!-- AFTER:
	insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) 
	values(employees_seq.nextval,#{lastName},#{email}) -->
</insert>

第五章 参数处理

5.1 封装参数

  • 单个参数:mybatis不会做特殊处理 #{参数名/任意值}取值

  • 多个参数:mybatis会做特殊处理

    • 通常操作

      • 方法public Employee getEmpByIdAndLastName(Integer id,String lastName);
      • 取值 #{id},#{lastName}
    • 上述操作会抛出异常Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter ‘id’ not found.

    • 多个参数会被mybatis拼接为一个map

      • key:param1…paramN或者参数的索引 0 ,1
      • value:传入的参数值
    • #{} 获取指定key的值

  • 命名参数:明确指定封装参数时map的key;@Param(“id”)

    Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
    
    • 多个参数会被封装成 一个map
      • key:使用@Param注解指定的值
      • value:参数值
    • #{指定的key}
  • POJO/MAP/TO

    • POJO 如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo

    • Map 如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map

    • TO 如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象,如:’

      Page{
      	int index;
      	int size;
      }
      
  • 扩展思考

    public Employee getEmp(@Param(“id”)Integer id,String lastName);

    • 取值:id==>#{id/param1} lastName==>#{param2}

    public Employee getEmp(Integer id,@Param(“e”)Employee emp);

    • 取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}

    如果参数是Collection(List、Set)类型或者是数组,

    • 也会特殊处理。也是把传入的list或者数组封装在map中
    • key:Collection(collection),如果是List还可以使用这个key(list)
    • public Employee getEmpById(List ids);
      • 取值:取出第一个id的值: #{list[0]}
  • 源码分析

    EmployeeDao employeeDao = openSession.getMapper(EmployeeDao.class);获取的是代理对象

    • (@Param(“id”)Integer id,@Param(“lastName”)String lastName);

    • ParamNameResolver类中的getNamedParams解析参数封装map的;

    • names:{0=id, 1=lastName};构造器的时候就确定好了

      1.获取每个标了param注解的参数的@Param的值:id,lastName; 赋值给name;

      2.每次解析一个参数给map中保存信息:(key:参数索引,value:name的值)

      name的值:
      标注了param注解:注解的值

      没有标注:
      1.全局配置:useActualParamName(jdk1.8):name=参数名
      2.name=map.size();相当于当前元素的索引
      {0=id, 1=lastName,2=2}

      public Object getNamedParams(Object[] args) {
        final int paramCount = names.size();
        if (args == null || paramCount == 0) {
          return null;
        } else if (!hasParamAnnotation && paramCount == 1) {
          return args[names.firstKey()];
        } else {
          final Map<String, Object> param = new ParamMap<Object>();
          int i = 0;
          for (Map.Entry<Integer, String> entry : names.entrySet()) {
            param.put(entry.getValue(), args[entry.getKey()]);
            // add generic param names (param1, param2, ...)
            final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
            // ensure not to overwrite parameter named with @Param
            if (!names.containsValue(genericParamName)) {
              param.put(genericParamName, args[entry.getKey()]);
            }
            i++;
          }
          return param;
        }
      }
      

5.2 参数取值

#{}和${}都可以获取map或实体类中的值

区别:

  • #{} : 是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
  • ${} : 取出的值直接拼装在sql语句中;会有安全问题;

大多情况下,我们去参数的值都应该去使用#{}

原生jdbc不支持占位符的地方我们就可以使用${}进行取值,比如分表、排序。。。;按照年份分表拆分

select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}

什么是sql注入

5.3 取值时指定参数规则

#{}:更丰富的用法:

规定参数的一些规则:

  • javaType、
  • jdbcType、
  • mode(存储过程)、
  • numericScale、
  • resultMap、
  • typeHandler、
  • jdbcTypeName、
  • expression(未来准备支持的功能);

jdbcType通常需要在某种特定的条件下被设置:

在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle DB(报错);
JdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,Oracle DB不能正确处理;由于全局配置中:jdbcTypeForNull=OTHER,Oracle DB不支持,两种解决方法:

  1. 在mapper文件中写#{email,jdbcType=NULL};
  2. 在全局配置文件<setting name="jdbcTypeForNull" value="NULL"/>

第六章 返回类型处理

6.1 返回list集合

<!--返回一个list,接口中是List<实体类>,mapper中的resultType仍然是类的全路径-->
<select id="getEmpList" resultType="com.qy.mybatis.entity.Employee">
   select * from employee
</select>

6.2 返回map

//返回多条通过@MapKey指定返回的map中key对应的字段
@MapKey("lastName")
Map<String,Employee> getEmpReturnMap();
//返回一条记录的map;key就是列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);

	<!--映射文件中仍然是Employee类路径-->
	<select id="getEmpReturnMap" resultType="com.qy.mybatis.entity.Employee">
		select * from employee
	</select>

6.3 自定义结果映射

<!--
      自定义某个javaBean的封装规则
         type:自定义规则的Java类型
         id:唯一id方便引用
  -->

<resultMap id="empMap" type="com.qy.mybatis.entity.Employee">
   <!--指定主键列的封装规则
   id定义主键会底层有优化;
   column:指定哪一列
   property:指定对应的javaBean属性
     -->
   <id column="id" property="id"></id>
   <!-- 定义普通列封装规则 -->
   <result column="last_name" property="lastName"></result>
   <!-- 其他不指定的列会自动封装:我们只要写resultMap就把全部的映射规则都写上。 -->
   <result column="gender"></result>
   <result column="email" property="email"></result>
</resultMap>

<select id="getEmpByIdWithResultMap" resultMap="empMap">
   select id,last_name ,email,gender from employee where id = #{id}
</select>

6.4 关联查询结果处理

6.4.1 环境搭建

部门和员工实体类搭建

public class Department {

   private Integer id;
   private String departmentName;
   private List<Employee> empList;
}
public class Employee {
   
   private Integer id;
   private String lastName;
   private String email;
   private String gender;
   private Department department;
   }

数据库中 员工表的外键department_id是部门表的主键

6.4.2 级联属性封装结果

a>类中嵌套类

员工和部门之间的关系

查询员工信息的时候把部门名称也查询出来

(1)普通级联映射

<resultMap id="getEmpAndDeptResult" type="com.qy.mybatis.entity.Employee">
		<id column="id" property="id"></id>
		<result column="last_name" property="lastName"></result>
		<result column="gender" property="gender"></result>
		<result column="email" property="email"></result>
		<!--department要和Employee中的Department属性名称保持一直-->
		<result column="d_id" property="department.id"></result>
		<result column="department_name" property="department.departmentName"></result>
	</resultMap>

	<select id="getEmpAndDept" resultMap="getEmpAndDeptResult">
		select
		 e.id id,
		 e.last_name lastName,
		 e.gender gender,
		 e.email email,
		 e.department_id department_id,
		 d.id d_id,
		 d.department_name department_name
		 from  employee e ,department d
		 where
		 e.department_id = d.id
		 and e.id=#{id}
	</select>

(2)association定义关联对象封装规则

<resultMap id="getEmpAndDeptByAssociationResult" type="com.qy.mybatis.entity.Employee">
   <id column="id" property="id"></id>
   <result column="lastName" property="lastName"></result>
   <result column="gender" property="gender"></result>
   <result column="email" property="email"></result>
   <!--
      association标签作用:指定联合的javaBean对象
      property 和Employee类中定义的Department属性名称保持一直
      javaType  类中嵌套的类的类路径
   -->
   <association property="department" javaType="com.qy.mybatis.entity.Department">
      <id column="d_id" property="id"></id>
      <result column="department_name" property="departmentName"></result>
   </association>
</resultMap>


<select id="getEmpAndDeptByAssociation" resultMap="getEmpAndDeptByAssociationResult">
   select
    e.id id,
    e.last_name lastName,
    e.gender gender,
    e.email email,
    e.department_id department_id,
    d.id d_id,
    d.department_name department_name
    from  employee e ,department d
    where
    e.department_id = d.id
    and e.id=#{id}
</select>

(3)association分步查询

DepartmentDao.java

public interface DepartmentDao {
    Department getDeptById(Integer id);

}

DepartmentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qy.mybatis.dao.DepartmentDao">

    <select id="getDeptById" resultType="com.qy.mybatis.entity.Department" >
		select id,department_name  departmentName from department where id = #{id}
	</select>

</mapper>


mybatis-config.xml

    <!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
    <mappers>
        <mapper resource="EmployeeMapper.xml" />
        <mapper resource="DepartmentMapper.xml" />
    </mappers>

EmployeeMapper.xml

<resultMap id="gerEmpByIdStepResult" type="com.qy.mybatis.entity.Employee">

		<id column="id" property="id"></id>
		<result column="lastName" property="lastName"></result>
		<result column="gender" property="gender"></result>
		<result column="email" property="email"></result>
		<!--select 属性 指定我们第二步查询要查的方法  column属性指定将哪一列的值传给这个方法-->
		<association property="department" select="com.qy.mybatis.dao.DepartmentDao.getDeptById" column="department_id">

		</association>

	</resultMap>


	<select id="getEmpByIdStep" resultMap="gerEmpByIdStepResult">
		select
		 e.id id,
		 e.last_name lastName,
		 e.gender gender,
		 e.email email,
		 e.department_id department_id
		 from  employee e
		 where
		 e.id=#{id}
	</select>

(4)分步查询&延迟加载

我们每次查询Employee对象的时候,都将一起查询出来。部门信息在我们使用的时候再去查询;分段查询的基础之上加上两个配置:

在全局配置文件中配置,实现懒加载

mybatis-config.xml

    <!--开启懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>
        
    <!--级联关系立即加载配置-->
        <setting name="aggressiveLazyLoading" value="false"/> 

(5)collection封装结果

比如 我们要查询一个部门下的所有员工

DepartmenDao

Department getDeptByIdPlus(Integer id);

DepartmenMapper.xml

<resultMap id="getDeptByIdPlusResult" type="com.qy.mybatis.entity.Department">
		<id column="id" property="id"></id>
		<result column="departmentName" property="departmentName"></result>
		<!-- 
			collection定义关联集合类型的属性的封装规则 
			ofType:指定集合里面元素的类型
		-->
		<collection property="empList" ofType="com.qy.mybatis.entity.Employee">
			<id column="e_id" property="id"></id>
			<result column="lastName" property="lastName"></result>
			<result column="gender" property="gender"></result>
			<result column="email" property="email"></result>
		</collection>
	</resultMap>

	<select id="getDeptByIdPlus" resultMap="getDeptByIdPlusResult">
		select
		d.id id,
		d.department_name departmentName,
		e.id e_id,
		e.last_name lastName,
		e.gender gender,
		e.email email
		from department d  left join employee e on d.id = e.department_id
		where d.id = 1
	</select>

(5)collection封装结果-延迟加载

DepartmentDao

Department getDeptByIdStep(Integer id);

DepartmentMapper

	<resultMap id="getDeptByIdStepResult" type="com.qy.mybatis.entity.Department">

		<id column="id" property="id"></id>
		<result column="departmentName" property="departmentName"></result>

		<collection property="empList" select="com.qy.mybatis.dao.EmployeeDao.getEmpListByDept" column="id">

		</collection>

	</resultMap>


	<select id="getDeptByIdStep" resultMap="getDeptByIdStepResult" >
		select id,department_name  departmentName from department where id = #{id}
	</select>

(6)分步查询传递多列值&fetchType

  • 多列的值传递过去:
    • 将多列的值封装map传递;column="{key1=column1,key2=column2}"
    • key的第二步查询中#{}的参数名
  • fetchType=“lazy”:表示使用延迟加载;
    • lazy:延迟 默认
    • eager:立即
<resultMap id="getDeptByIdStepResult" type="com.qy.mybatis.entity.Department">

   <id column="id" property="id"></id>
   <result column="departmentName" property="departmentName"></result>

   <collection property="empList" select="com.qy.mybatis.dao.EmployeeDao.getEmpListByDept" column="{deptId=id}" fetchType="lazy">

   </collection>

</resultMap>


<select id="getDeptByIdStep" resultMap="getDeptByIdStepResult" >
   select id,department_name  departmentName from department where id = #{id}
</select>

第七章 动态sql

  • 动态SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作。

  • 动态SQL元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似。

  • MyBatis采用功能强大的基于 OGNL 的表达式来简化操作

  • if

  • choose (when, otherwise)

  • trim (where, set)

  • foreach

7.1 动态sql-if-判断&OGNL

DynamicSQLMapper

public interface DynamicSQLMapper {
   //携带了哪个字段查询条件就带上这个字段的值
   public List<Employee> getEmpListByConditionIf(Employee employee);
   
}

DynamicSQLMapper.xml

<select id="getEmpListByConditionIf" resultType="com.qy.mybatis.entity.Employee">
   select *
   from employee
   where 1=1
   <!-- test:判断表达式(OGNL)
   OGNL参照PPT或者官方文档。
       c:if  test
   从参数中取值进行判断
   遇见特殊符号应该去写转义字符:
   &&:
   -->
   <if test="id!=null and id!=''">
      and id=#{id}
   </if>
   <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
      and last_name like #{lastName}
   </if>
   <if test="email!=null and email.trim()!=&quot;&quot;">
      and email=#{email}
   </if>
   <!-- ognl会进行字符串与数字的转换判断  "0"==0 -->
   <if test="gender==0 or gender==1">
      and gender=#{gender}
   </if>

</select>

7.2 动态sql-where-查询条件

查询的时候如果某些条件没带可能sql拼装会有问题

1.给where后面加上1=1,以后的条件都and xxx。
2.mybatis使用where标签来将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,多出来的and或者or去掉(where只会去掉第一个多出来的and或者or,但最后一个多出来的and或者or则不会去掉。

DynamicSQLMapper

public List<Employee> getEmpListByConditionWhere(Employee employee);

DynamicSQLMapper.xml

<select id="getEmpListByConditionWhere" resultType="com.qy.mybatis.entity.Employee">
   select *
   from employee

   <where>
      <if test="id!=null and id!=''">
         and id=#{id}
      </if>
      <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
         and last_name like #{lastName}
      </if>
      <if test="email!=null and email.trim()!=&quot;&quot;">
         and email=#{email}
      </if>
      <!-- ognl会进行字符串与数字的转换判断  "0"==0 -->
      <if test="gender==0 or gender==1">
         and gender=#{gender}
      </if>
   </where>
</select>

7.3 动态sql-trim-自定义字符串截取

trim标签 相关属性
-prefix 给拼串后的整个字符串加一个前缀
-prefixOverrides 去掉整个字符串前面多余的字符
-suffix 拼串后的整个字符串加一个后缀
-suffixOverrides 去掉整个字符串后面多余的字符

DynamicSQLMapper

	public List<Employee> gerEmpListByConditionTrim(Employee employee);

DynamicSQLMapper.xml

<select id="gerEmpListByConditionTrim" resultType="com.qy.mybatis.entity.Employee">
			select *
			from employee
			<!--
			trim标签
			 -prefix  给拼串后的整个字符串加一个前缀
			 -prefixOverrides 去掉整个字符串前面多余的字符
			 -suffix 拼串后的整个字符串加一个后缀
			 -suffixOverrides 去掉整个字符串后面多余的字符
			-->
			<trim prefix="where" prefixOverrides="and"  suffixOverrides="and">
				<if test="id!=null and id!=''">
					and id=#{id}
				</if>
				<if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
					and last_name like #{lastName}
				</if>
				<if test="email!=null and email.trim()!=&quot;&quot;">
					and email=#{email}
				</if>
				<!-- ognl会进行字符串与数字的转换判断  "0"==0 -->
				<if test="gender==0 or gender==1">
					and gender=#{gender}
				</if>
			</trim>



	</select>

7.4 动态sql-choose-分支选择

choose相当于 case when break 分支选择 选择其中的一个

DynamicSQLMapper

public List<Employee> getEmpListByConditionChoose(Employee employee);

DynamicSQLMapper.xml

<select id="getEmpListByConditionChoose" resultType="com.qy.mybatis.entity.Employee">
		select *
		from employee
		<where>
			<choose>
				<when test="id!=null and id!=''">
					id=#{id}
				</when>
				<when test="lastName!=null and lastName!=''">
					last_name like#{lastName}
				</when>
				<otherwise>
					gender=#{gender}
				</otherwise>
			</choose>
		</where>
	</select>

7.5 动态sql-set-与if结合的动态更新

set标签中可以动态set值,更新我们需要更新的字段

DynamicSQLMapper

	public void updateEmp(Employee employee);

DynamicSQLMapper.xml

	<update id="updateEmp">
		update  employee
		<set>
			<if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">
				 last_name = #{lastName},
			</if>
			<if test="email!=null and email.trim()!=&quot;&quot;">
				 email=#{email},
			</if>
		</set>
		where
		id = #{id}

	</update>

7.6 动态sql-foreach-遍历集合

collection:指定要遍历的集合:

list类型的参数会特殊处理封装在map中,map的key就叫list
item:将当前遍历出的元素赋值给指定的变量

separator:每个元素之间的分隔符

open:遍历出所有结果拼接一个开始的字符

close:遍历出所有结果拼接一个结束的字符

index:索引。遍历list的时候是index就是索引,item就是当前值

遍历map的时候index表示的就是map的key,item就是map的值
#{变量名}就能取出变量的值也就是当前遍历出的元素

DynamicSQLMapper

public List<Employee> getEmpListByConditionForeach(@Param("ids") List<Integer> ids);

DynamicSQLMapper.xml

	<select id="getEmpListByConditionForeach" resultType="com.qy.mybatis.entity.Employee">
		select *
		from employee

		<where>
			id in

			<foreach collection="ids" item="item" separator="," open="(" close=")">
				#{item}
			</foreach>
		</where>
	</select>

7.7 动态sql-foreach-mysql下foreach批量插入的两种方式

mysql支持 insert 表名(字段) values (),(),()方式 和 insert 表名 values();insert 表名 values();insert 表名 values(); 两种方式

方式一:

DynamicSQLMapper

	public void  insertForeach1(@Param("employeeList") List<Employee> employeeList);

DynamicSQLMapper.xml

	<insert id="insertForeach1">
		insert  employee(last_name,email,gender,department_id)
		values
		<foreach collection="employeeList" item="emp" separator="," >
			(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.department.id})
		</foreach>


	</insert>

方式二

注意这种方式需要配置数据库连接url属性allowMultiQueries=true

jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=CTT&amp;allowMultiQueries=true"

DynamicSQLMapper

	public void  insertForeach2(@Param("employeeList") List<Employee> employeeList);

DynamicSQLMapper.xml

<insert id="insertForeach2">

   <foreach collection="employeeList" item="emp" separator=";" >
      insert into employee(last_name,email,gender,department_id)
      values(
      #{emp.lastName},
      #{emp.email},
      #{emp.gender},
      #{emp.department.id})
   </foreach>

</insert>

7.8动态sql-foreach-oracle下批量插入的两种方式

Oracle数据库批量保存:

  • Oracle不支持values(),(),()

Oracle支持的批量方式:

  1. 多个insert放在begin - end里面
  2. 利用中间表
# 多个insert放在begin - end里面
begin
    insert into employees(employee_id,last_name,email) 
    values(employees_seq.nextval,'test_001','test_001@atguigu.com');
    insert into employees(employee_id,last_name,email) 
    values(employees_seq.nextval,'test_002','test_002@atguigu.com');
end;

# 利用中间表
insert into employees(employee_id,last_name,email)
   select employees_seq.nextval,lastName,email from(
          select 'test_a_01' lastName,'test_a_e01' email from dual
          union
          select 'test_a_02' lastName,'test_a_e02' email from dual
          union
          select 'test_a_03' lastName,'test_a_e03' email from dual
   );

方式一

<insert id="addEmps" databaseId="oracle">
	<!-- oracle第一种批量方式 -->
	<foreach collection="emps" item="emp" open="begin" close="end;">
		insert into employees(employee_id,last_name,email) 
	    values(employees_seq.nextval,#{emp.lastName},#{emp.email});
	</foreach> 
</insert>
	


方式二

<insert>
	<!-- oracle第二种批量方式  -->
	insert into employees(employee_id,last_name,email)
	<foreach collection="emps" item="emp" separator="union"
		open="select employees_seq.nextval,lastName,email from("
		close=")">
		select #{emp.lastName} lastName,#{emp.email} email from dual
	</foreach>
</insert>

7.9 动态sql-内置参数 _parameter & _databaseId

不只是方法传递过来的参数可以被用来判断,

mybatis默认还有两个内置参数

_parameter:代表整个参数
单个参数:_parameter就是这个参数
多个参数:参数会被封装为一个map;_parameter就是代表这个map
_databaseId:如果配置了databaseIdProvider标签。
_databaseId就是代表当前数据库的别名oracle

<!--public List<Employee> getEmpsTestInnerParameter(Employee employee);  -->
<select id="getEmpsTestInnerParameter" resultType="com.lun.c01.helloworld.bean.Employee">

	<if test="_databaseId=='mysql'">
		select * from tbl_employee
		<if test="_parameter!=null">
			where last_name like #{_parameter.lastName}
		</if>
	</if>

	<if test="_databaseId=='oracle'">
		select * from employees
		<if test="_parameter!=null">
			where last_name like #{_parameter.lastName}
		</if>
	</if>
</select>

7.10 动态sql-bind-绑定

<!--public List<Employee> getEmpsTestInnerParameter(Employee employee);  -->
<select id="getEmpsTestInnerParameter" resultType="com.atguigu.mybatis.bean.Employee">

		<!-- bind:可以将OGNL表达式的值绑定到一个变量中,方便后来引用这个变量的值 -->
		<bind name="lastName" value="'%'+lastName+'%'"/>

		<if test="_databaseId=='mysql'">
			select * from tbl_employee
			<if test="_parameter!=null">
				where last_name like #{lastName}<!-- 这里使用到lastName -->
			</if>
		</if>
		<if test="_databaseId=='oracle'">
			select * from employees
			<if test="_parameter!=null">
				where last_name like #{_parameter.lastName}
			</if>
		</if>
</select>

7.11 动态sql-sql-抽取可重用的sql片段

抽取可重用的sql片段。方便后面引用:

sql抽取:经常将要查询的列名,或者插入用的列名抽取出来方便引用
include来引用已经抽取的sql:
include还可以自定义一些property,sql标签内部就能使用自定义的属性
include-property:取值的正确方式${prop} 不能使用#

DynamicSQLMapper

public List<Employee> getEmpListBySQL();

DynamicSQLMapper.xml

	<select id="getEmpListBySQL" resultType="com.qy.mybatis.entity.Employee">
		select
		<include refid="selectSql"></include>
		from employee
	</select>

第八章 缓存

MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存,一级缓存和二级缓存。

默认情况下,只有一级缓存( SqlSession级别的缓存,也称为本地缓存)开启。
二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
为了提高扩展性。 MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

8.1 一级缓存

一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后, 该Session 中的所有 Cache 将被清空。
本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.
在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis.xml 中配置

同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中

key = hashCode + 查询的SqlId + 编写的sql查询语句 + 参数
一级缓存失效的四种情况:

不同的SqlSession对应不同的一级缓存
同一个SqlSession但是查询条件不同
同一个SqlSession两次查询期间执行了任何一次增删改操作
同一个SqlSession两次查询期间手动清空了缓存

8.2 二级缓存

  • 二级缓存(second level cache),全局作用域缓存

  • 二级缓存默认不开启,需要手动配置

  • MyBatis提供二级缓存的接口以及实现,缓存实现要求 POJO实现Serializable接口

  • 二级缓存在 SqlSession 关闭或提交之后才会生效

  • 使用步骤

    1. 全局配置文件中开启二级缓存
      • <setting name="cacheEnabled" value="true"/>
    2. 需要使用二级缓存的映射文件处使用cache配置缓存
      • <cache />
    3. 注意: POJO需要实现Serializable接口

cache标签的属性:

eviction:缓存的回收策略:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,设置一个毫秒值
readOnly:是否只读:
true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
false:非只读:mybatis觉得获取的数据可能会被修改。mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
size:缓存存放多少元素;
type=“”:指定自定义缓存的全类名;
实现Cache接口即可;
tip:mybatis必须是会话关闭或者提交后 一级缓存的数据才会转移到二级缓存中

代码演示

1.全局配置文件中配置二级缓存

        <!--开启二级缓存 默认开启-->
        <setting name="cacheEnabled" value="true"/>

2.mapper文件中添加cache标签

<cache></cache>

3.实体类实现序列号接口

public class Employee  implements Serializable 

4.测试

 @Test
    public void test22() throws IOException {

        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession1 = sqlSessionFactory.openSession();
        SqlSession openSession2 = sqlSessionFactory.openSession();
        try {
            EmployeeDao mapper1 = openSession1.getMapper(EmployeeDao.class);
            EmployeeDao mapper2 = openSession2.getMapper(EmployeeDao.class);
            Employee empById1 = mapper1.getEmpById(2);
            System.out.println(empById1);
            /*先关闭1号会话*/
            openSession1.close();
            Employee empById2 = mapper2.getEmpById(2);
            System.out.println(empById2);

            openSession2.close();
        }finally {

        }

    }

8.3 和缓存相关的设置

全局setting的cacheEnable:
– 配置二级缓存的开关。一级缓存一直是打开的。
select标签的useCache属性:
– 配置这个select是否使用二级缓存。一级缓存一直是使用的
每个增删改查标签的flushCache属性:
– 增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。

– 询默认flushCache=false。
sqlSession.clearCache():
– 只是用来清除一级缓存。
全局setting的localCacheScope:

-本地缓存作用域:SESSION:当前会话的所有数据保存在会话缓存中;STATEMENT:可以禁用一级缓存。

8.4 缓存示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kWcsJJ4P-1650933511434)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220224130400636.png)]

8.5 第三方缓存整合原理

  • EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
  • MyBatis定义了Cache接口方便我们进行自定义扩展
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    ReadWriteLock getReadWriteLock();
}

整合步骤

1.导入依赖

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

2.编写ehcache.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">
    <!-- 磁盘保存路径 -->
    <diskStore path="D:\\ehcache" />

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

        <!--
        属性说明:
        l diskStore:指定数据在磁盘中的存储位置。
        l defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

        以下属性是必须的:
        l maxElementsInMemory - 在内存中缓存的element的最大数目
        l maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大
        l eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
        l overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

        以下属性是可选的:
        l timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
        l timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
         diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
        l diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
        l diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
        l memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
         -->

3.mapper映射文件中添加cache标签 并指明type

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

另外

参照缓存: 若想在命名空间中共享相同的缓存配置和实例。可以使用 cache-ref 元素来引用另外一个缓存。

<cache-ref namespace="com.qy.mybatis.dao.EmployeeDao" ></cache-ref>

第九章 SSM整合

1.配置web.xml

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>


  <!--Spring配置: needed for ContextLoaderListener -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <!--
  启动Web容器时,读取在contextConfigLocation中定义的xml文件,
  自动装配ApplicationContext的配置信息,并产生WebApplicationContext对象,
  然后将这个对象放置在ServletContext的属性里-->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  
  <!--配置SpringMVC的前端控制器-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>

      <!-- contextConfigLocation为固定值 -->
      <param-name>contextConfigLocation</param-name>
      <!-- 指定我们的SpringMVC配置文件的路径
      使用classpath:表示从类路径查找配置文件,例如maven工程中的src/main/resources -->
      <param-value>classpath:springMVC.xml</param-value>
    </init-param>

    <!--随着服务器启动 进行初始化-->
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

</web-app>

2.配置SpringMVC.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">



    <!--SpringMVC只是控制网站跳转逻辑  -->
    <!-- 只扫描控制器 -->
    <context:component-scan base-package="com.qy.ssm.controller" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>


    <!-- 视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!--SpringMVC注解驱动-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <!--默认Servlet处理器处理静态资源请求-->
    <mvc:default-servlet-handler/>

    <mvc:view-controller path="/index" view-name="index"></mvc:view-controller>


</beans>

3.配置applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">

    <!-- Spring希望管理所有的业务逻辑组件,等。。。 -->
    <context:component-scan base-package="com.qy.ssm">
        <context:exclude-filter type="annotation"
                                expression="org.springframework.stereotype.Controller" />
    </context:component-scan>


    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=CTT" />
        <property name="username" value="root" />
        <property name="password" value="199787" />
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
    </bean>

    <!-- spring事务管理 -->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 开启基于注解的事务 -->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--
	整合mybatis
		目的:1、spring管理所有组件。mapper的实现类。
				service==>Dao   @Autowired:自动注入mapper;
			2、spring用来管理事务,spring声明式事务
	-->
    <!--创建出SqlSessionFactory对象  -->
    <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <!-- configLocation指定全局配置文件的位置 -->
        <property name="configLocation" value="classpath:/mybatis-config.xml"></property>
        <!--mapperLocations: 指定mapper文件的位置-->
        <property name="mapperLocations" value="classpath:/mapper/*Mapper.xml"></property>
    </bean>

    <!--配置一个可以进行批量执行的sqlSession  -->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
        <constructor-arg name="executorType" value="BATCH"></constructor-arg>
    </bean>

    <!-- 扫描所有的mapper接口的实现,让这些mapper能够自动注入;
    base-package:指定mapper接口的包名
     -->
    <!--<mybatis-spring:scan base-package="com.qy.ssm.dao"/>-->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.qy.ssm.dao"></property>
    </bean>







</beans>

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>




    <!--
        2、settings包含很多重要的设置项
		setting:用来设置每一个设置项
			name:设置项名
			value:设置项取值
    -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>


        <!--开启懒加载-->
        <setting name="lazyLoadingEnabled" value="true"/>

        <!--级联关系立即加载配置-->
        <setting name="aggressiveLazyLoading" value="false"/>

        <!--开启二级缓存 默认开启-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

</configuration>

第十章 代码生成器

略 网上看攻略

第十一章 Mybatis运行原理

1.创建sqlSessionFactory

2.openSession开启会话

3.获得mapper接口代理对象

4.调用代理对象的方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vwyln8WX-1650933511435)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220225083751245.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifupe6TQ-1650933511436)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220225083804092.png)]

11.1 创建SQLSessionFactory对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4SFKbF56-1650933511436)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220225084202038.png)]

创建SQLSessionFactory流程

    *   1.读取全局配置文件 作为输入流调用SqlSessionFactoryBuilder.build(inputStream)方法
    *   2.build(inputStream)方法中调用重载的build(inputStream)方法
    *   3.XMLConfigBuilder 创建XML解析器Xpath调用parse解析配置文件内容
    *   4.parse方法中调用parseConfiguration(parser.evalNode("/configuration"))方法 解析<configuration>标签体内容
    *   4.parseConfiguration方法读取全局配置,把配置加载到Configuration方中,通过			                  mapperElement(root.evalNode("mappers"));读取mapper映射文件
    *   5.mapperElement中通过XMLMapperBuilder调用parse()解析mapper映射文件的内容
    *   6.XMLMapperBuilder的parse()方法中通过configurationElement方法读取mapper文件内容
    *   7.buildStatementFromContext 读取我们的sql语句
    *   8.buildStatementFromContext方法中通过XMLStatementBuilder对象调用parseStatementNode读取增删改查方法中的
    *     标签和sql,最后通过builderAssistant.addMappedStatement方法返回一个MappedStatement对象并加载到Configuration*   9.最后通过SqlSessionFactory build(Configuration config)返回一个DefaultSqlSessionFactory对象

创建SQLSessionFactory

​ 解析文件中的每一个信息保存到Configuration中,返回包含Configuration 的DefaultSqlSessionFactory对象

​ MapperdStatement代表一个增删改查的方法的相信信息

11.2 openSession()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uMdnad4U-1650933511436)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220226111457810.png)]

openSession执行流程

/*
* 1.openSession() 中调用openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
* 2.openSessionFromDataSource()中获取配置信息 包括环境、事务管理器等 并创建了一个Executor对象 Executor executor =     configuration.newExecutor(tx, execType);
* 3.newExecutor中根据executorType创建不同类型的Executor对象 包括BatchExecutor、ReuseExecutor、SimpleExecutor
   如果设置了二级缓存配置 再对生成的Executor对象进行封装 生成二级缓存的Executor对象  executor = new CachingExecutor(executor);
* 4.创建好Executor后 又执行了  executor = (Executor) interceptorChain.pluginAll(executor); 过滤器执行链 和插件有关
* 5.最后把Executor对象和Configuration作为参数返回一个DefaultSqlSession对象
* */

11.3 getMapper

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-voW4Sx3f-1650933511437)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220226113618841.png)]

11.4 执行方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8j4UcFHO-1650933511437)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220228130749846.png)]

执行流程:

1.调用MapperProxy的invoke方法

2.在invoke方法中调用execute方法 参数为sqlSession对象和方法参数

3.execute方法中判断sql的类型(增删改查),然后进行paramNameResolver进行参数封装并执行sqlSession的selectOne方法

4.在DefaultSqlSession中selectOne方法中调用selectList方法,取出第一条

5.selectList方法中,获取configuration中的MapperStatement对象 获取执行方法的具体信息,然后再调用executor.query方法

6.如果设置了二级缓存调用CachingExecutor中的query方法,获取BoundSql,代表要执行的sql

7.如果缓存中没有数据,调用真正执行器的query方法调用queryFromDatabase,查出数据后把数据也放在本地缓存

8.执行doQuery方法 创建StatementHandler对象(PreparedStatementHandler 实现了StatementHandler接口)实例是PreparedStatementHandler 初始化过程中创建ParmeHandler和resultSetHandler

9.(StatementHandler) interceptorChain.pluginAll(statementHandler);

10.(ParameterHandler) interceptorChain.pluginAll(parameterHandler);

11.(ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);

12.预编译sql尝试PreparedStatement对象

13.调用ParmeHandler设置参数

14.使用TypeHandler给sql与编写设置参数

15.调用PreparedStatementHandler的query方法执行sql使用ResultSetHandler处理结果,使用TypeHander获取value值

11.5 查询流程小结

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xNC839Pl-1650933511437)(C:\Users\wcy\AppData\Roaming\Typora\typora-user-images\image-20220228190718782.png)]

  • StatementHandler:处理sql语句预编译,设置参数等相关工作;
  • ParameterHandler:设置预编译参数用的
  • ResultHandler:处理结果集
  • TypeHandler:在整个过程中,进行数据库类型和javaBean类型的映射

11.7 Mybatis运行流程

根据配置文件(全局,sql映射)初始化出Configuration对象
创建一个DefaultSqlSession对象,它里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)
DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;
MapperProxy里面有(DefaultSqlSession);
执行增删改查方法:
调用DefaultSqlSession的增删改查(Executor);
会创建一个StatementHandler对象。同时也会创建出ParameterHandler和ResultSetHandler)
调用StatementHandler预编译参数以及设置参数值,使用ParameterHandler来给sql设置参数
调用StatementHandler的增删改查方法;
ResultSetHandler封装结果
注意:四大对象(Executor、ParameterHandler、ResultSetHandler)每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);

第十二章 插件开发

在四大对象创建的时候

每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
获取到所有的Interceptor(拦截器)(插件需要实现的接口);调用interceptor.plugin(target);返回target包装后的对象
插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)我们的插件可以为四大对象创建出代理对象;代理对象就可以拦截到四大对象的每一个执行;

public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
插件开发的步骤

  1. 编写插件实现Interceptor接口,并使用 @Intercepts注解完成插件签名

    package com.qy.mybatis.Interceptors;
    
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.*;
    
    import java.util.Properties;
    
    
    
    /*
    * 拦截的对象,type为拦截哪个类,method拦截哪个方法,args:拦截参数 防止重载无法识别
    * */
    @Intercepts({
            @Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
    })
    public class FirstPlugin  implements Interceptor {
    
        /*
        * 1.调用目标方法
        */
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("FirstPlugin-->intercept");
            /*
            *invocation.proceed()表示放行执行目标方法
            * */
            System.out.println("拦截到的对象是"+invocation.getTarget());
            Object proceed = invocation.proceed();
            return proceed;
        }
        /*
        *包装目标对象,为目标对象创建一个代理对象
        **/
        public Object plugin(Object target) {
            /*通过Plugin类的wrap返回一个代理对象*/
            System.out.println("被包装的对象是"+target);
            Object wrap = Plugin.wrap(target, this);
            return wrap;
        }
        /*
        *将插件注册时的property属性设置进来
        */
        public void setProperties(Properties properties) {
            System.out.println(properties);
    
        }
    }
    
    
  2. 在全局配置文件中注册插件

        <plugins>
            <plugin interceptor="com.qy.mybatis.Interceptors.FirstPlugin">
                <property name="username" value="qianyue"/>
            </plugin>
        </plugins>
    
    

    插件原理

    按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
    多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
    目标方法执行时依次从外到内执行插件的intercept方法。
    多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值