MyBatis

6 篇文章 0 订阅

一.下载MyBatis

github下载地址

二.MyBatis操作数据库

  1. 创建MyBatis全局配置文件

/conf/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">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>
	<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
	<mappers>
		<mapper resource="EmployeeMapper.xml" />
	</mappers>
</configuration>
  1. 创建sql映射文件

/conf/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="com.atguigu.mybatis.dao.EmployeeMapper">
<!-- 
namespace:名称空间;指定为接口的全类名
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值

public Employee getEmpById(Integer id);
 -->
	<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee">
		select id,last_name lastName,email,gender from tbl_employee where id = #{id}
	</select>
</mapper>
  1. 测试
  • 根据全局配置文件,利用SqlSessionFactoryBuilder创建SqlSessionFactory
  • 使用SqlSessionFactory获取sqlSession对象。一个SqlSession对象代表和数据库的一次会话。
  • 使用SqlSession根据方法id进行操作
package com.atguigu.mybatis.test;

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

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 com.atguigu.mybatis.bean.Employee;
import com.atguigu.mybatis.dao.EmployeeMapper;

/**
 * 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抽取出来。	
 * 
 * 
 * @author lfy
 *
 */
public class MyBatisTest {
	

    //根据全局配置文件,利用SqlSessionFactoryBuilder创建SqlSessionFactory
	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}

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

		// 2、获取sqlSession实例,能直接执行已经映射的sql语句
		// sql的唯一标识:statement Unique identifier matching the statement to use.
		// 执行sql要用的参数:parameter A parameter object to pass to the statement.
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			Employee employee = openSession.selectOne(
					"com.atguigu.mybatis.EmployeeMapper.selectEmp", 1);
			System.out.println(employee);
		} finally {
			openSession.close();
		}

	}

	@Test
	public void test01() throws IOException {
		// 1、获取sqlSessionFactory对象
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		// 2、获取sqlSession对象
		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			// 3、获取接口的实现类对象
			//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			Employee employee = mapper.getEmpById(1);
			System.out.println(mapper.getClass());
			System.out.println(employee);
		} finally {
			openSession.close();
		}

	}

}

  1. 注意点
  • SqlSession 的实例不是线程安全的,因此是不能被共享的。
  • SqlSession每次使用完成后需要正确关闭,这个关闭操作是必须的
  • SqlSession可以直接调用方法的id进行数据库操作,但是我们一般还是推荐使用SqlSession获取到Dao接口的代理类,执行代理对象的方法,可以更安全的进行类型检查操作

三.MyBatis全局配置文件

MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置(settings)和属性(properties)信息。文档的顶层结构如下:

configuration 配置

properties 属性

settings 设置

typeAliases 类型命名

typeHandlers 类型处理器

objectFactory 对象工厂

plugins 插件

environments 环境

environment 环境变量

transactionManager 事务管理器

dataSource 数据源

databaseIdProvider 数据库厂商标识

mappers 映射器

四.MyBatis映射文件

参数处理

#{}与${}取值区别

#{}:预编译形式,将参数设置到sql语句中,PreparedStatement;防止sql注入

${}:取出的值直接拼装在sql语句中,会有安全问题;

大多情况使用#{};

原生jdbc不支持占位符的地方我们就可以使用${}进行取值

比如分表、排序;按照年份分表拆分:

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

#{}:更丰富的用法:

规定参数的一些规则:

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

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

在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle(报错)。jdbcType OTHER:无效的类型;因为mybatis对所有的null都映射的是原生Jdbc的OTHER类型,oracle不能正确处理。

根本原因:mybatis全局配置中:jdbcTypeForNull=OTHER:oracle不支持。两种解决方式:

  1. 在sql语句中设置:#{字段名,jdbcType=null};
  2. 修改mybatis全局配置文件:jdbcTypeForNull=NULL

返回类型(resultType)

返回对象的list

resultType值设为对象的全类名

返回map

一条记录的map

key就是列名,值就是对应的值

resultType值设为map

多条记录的map

Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javabean

resultType值设为对象的全类名(xxx.xxx.Employee)

在mapper文件中,加注解:

//告诉mybatis封装这个map的时候使用哪个属性作为主键
@MapKey("id")
public Map<Integer,Employee> getEmpByLastNameLikeReturnMap(String lastname);

返回类型(resultMap)

association(定义对象,一个员工对应一个部门)

复杂对象映射,pojo中的属性可能是一个对象,使用联合查询

  • 以级联属性的方式封装对象
<resultMap type="com.xxx.xxx.Employee" id="MyDifEmp">
    <id column="id" property="id"/>    
	<result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="did" property="dept.id"/>
    <result column="dept_name" property="dept.departmentName"/>
</resultMap>

<select id="getEmpAndDept" resultMap="MyDifEmp">
    SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id,d.dept_name dept_name from tbl_employee e,tbl_dept d where e.d_id=d.id AND e.id=#{id}
</select>
  • association-嵌套结果集
<resultMap type="com.xxx.xxx.Employee" id="MyDifEmp">
    <id column="id" property="id"/>    
	<result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <association property="dept" javaType="com.xxx.xxx.Dept">
		<id column="did" property="id"/>
    	<result column="dept_name" property="departmentName"/>
    </association>
</resultMap>
  • association-分段查询
<resultMap type="com.xxx.xxx.Employee" id="MyDifEmp">
    <id column="id" property="id"/>    
	<result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <association property="dept" 
    	select="com.xxx.xxx.EmployeeMapper.getEmpAndDeptById"
	    colunm="did">
    </association>
</resultMap>

select:调用目标的方法查询当前属性的值

column:将指定列的值传入目标方法

  • association-分段查询&延迟加载

开启延迟加载和属性按需加载

<settings>
	<setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

collection(定义集合,一个部门对应多个员工)

  • Collection-集合类型&嵌套结果集
<resultMap type="com.xxx.xxx.Department" id="MyDept">
    <id column="did" property="id"/>    
	<result column="dept_name" property="departmentName"/>
    <result column="gender" property="gender"/>
    <collection property="emps" ofType="com.xxx.xxx.Employee" 
    	<id column="eid" property="id"/>
    	<result column="last_name" property="lastName"/>
    	<result column="email" property="email"/>
    	<result column="gender" property="gender"/>
    </collection>
</resultMap>

<select id="getDeptByIdPlus" resultMap="MyDept">
    SELECT d.id did,d.dept_name dept_name,e.id eid,e.last_name last_name,e.email email,e.gender gender 
    FROM tbl_dept d
    LEFT JOIN tbl_employee e
    ON d.id=e.d_id
    WHERE d.id=#{id}
    
</select>
  • Collection-分步查询&延迟加载
<resultMap type="com.xxx.xxx.Department" id="MyDeptStep">
    <id column="id" property="id"/>    
	<result column="dept_name" property="departmentName"/>
    <collection property="emps" 
    	select="com.xxx.xxx.EmployeeMapperPlus.getEmpsById"
	    column="id"
    >
    	<id column="eid" property="id"/>
    	<result column="last_name" property="lastName"/>
    	<result column="email" property="email"/>
    	<result column="gender" property="gender"/>
    </collection>
</resultMap>

<select id="getDeptByIdStep" resultMap="MyDeptStep">
    SELECT id,dept_name
    FROM tbl_dept
    WHERE id=#{id}
</select>

扩展-分布查询,多列值封装map传递

<resultMap type="com.xxx.xxx.Department" id="MyDeptStep">
    <id column="id" property="id"/>    
	<result column="dept_name" property="departmentName"/>
    <collection property="emps" 
    	select="com.xxx.xxx.EmployeeMapperPlus.getEmpsById"
	    column="id"
    <!-- column="{deptId=id}"  fetchType="lazy" -->
   >
    
    
    	<id column="eid" property="id"/>
    	<result column="last_name" property="lastName"/>
    	<result column="email" property="email"/>
    	<result column="gender" property="gender"/>
    </collection>
</resultMap>

<select id="getDeptByIdStep" resultMap="MyDeptStep">
    SELECT id,dept_name
    FROM tbl_dept
    WHERE id=#{id}
</select>
    
    <!--多列值封装map传递
    column="{key1=column1,key2=column2}"
    fetchType="lazy":表示使用延迟加载:
        lazy:延迟加载
        eager:立即加载
    -->

discrominator鉴别器

判断某列的值,然后根据某列的值改变封装行为。

如果查出女生:部门信息查询出来,否则不查询;

如果查出男生:把last_name这一列的值赋值给email;

<resultMap type="com.xxx.xxx.Employee" id="MyDifEmp">
    <id column="id" property="id"/>    
	<result column="last_name" property="lastName"/>
    <result column="email" property="email"/>
    <result column="gender" property="gender"/>
    <!--
    	column:指定判定的列名
        javaType:列值对应的java类型
    -->
    <discriminator javaType="string" column="gender">
    	<!--女生 rsultType:指定封装的结果类型,不可缺少(或者resultMap)-->
        <case value="0" rsultType="com.xxx.xxx.Employee">
            <association property="dept" 
    			select="com.xxx.xxx.EmployeeMapper.getEmpAndDeptById"
	    		colunm="did">
    		</association>
        </case>
            
        <!---男生->
        <case value="1">
            <id column="id" property="id"/>    
			<result column="last_name" property="lastName"/>
            <result column="last_name" property="email"/>
    		<result column="gender" property="gender"/>
        </case>
    </discriminator>
    
    
</resultMap>

五.MyBatis-动态SQL

  • if:判断

  • choose(when,otherwise)

  • trim(where,set)

    where:封装查询条件

    set:封装修改条件

  • foreach

1.if

<!-- 查询员工,要求,携带了哪个字段查询条件就带上这个字段的值 -->
<!-- public List<Employee> getEmpsByConditionIf(Employee employee); -->
<select id="getEmpsByConditionIf" resultType="com.atguigu.mybatis.bean.Employee">
    select * from tbl_employee
    <!-- where -->
    <where>
        <!-- test:判断表达式(OGNL)OGNL参照PPT或者官方文档。
        c:if  test
    从参数中取值进行判断

    遇见特殊符号应该去写转义字符:
    &&:&amp;&amp;
    -->
        <if test="id!=null">
            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>

查询的时候如果某些条件没带可能sql拼装会有问题,两种解决方案:

  1. 在where后面加上1=1,以后的条件都and xxx
  2. 使用标签,将条件放在标签内。mybatis会将where标签中拼装的sql,多出来的and或者or去掉。但是如果and或者or放在条件的后面(条件1 and),此时就可能出现问题(当最后一个条件没有时,sql语句末尾多一个and或者or),这个问题可以采用标签。

2.trim(where)

<!--public List<Employee> getEmpsByConditionTrim(Employee employee);  -->
<select id="getEmpsByConditionTrim" resultType="com.atguigu.mybatis.bean.Employee">
    select * from tbl_employee
    <!-- 后面多出的and或者or where标签不能解决 
   prefix="":前缀:trim标签体中是整个字符串拼串 后的结果。
     prefix给拼串后的整个字符串加一个前缀 
   prefixOverrides="":
     前缀覆盖: 去掉整个字符串前面多余的字符
   suffix="":后缀
     suffix给拼串后的整个字符串加一个后缀 
   suffixOverrides=""
     后缀覆盖:去掉整个字符串后面多余的字符

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

3.choose

<!-- public List<Employee> getEmpsByConditionChoose(Employee employee); -->
<select id="getEmpsByConditionChoose" resultType="com.atguigu.mybatis.bean.Employee">
    select * from tbl_employee 
    <where>
        <!-- 如果带了id就用id查,如果带了lastName就用lastName查;只会进入其中一个when(或者otherwise) -->
        <choose>
            <when test="id!=null">
                id=#{id}
            </when>
            <when test="lastName!=null">
                last_name like #{lastName}
            </when>
            <when test="email!=null">
                email = #{email}
            </when>
            <otherwise>
                gender = 0
            </otherwise>
        </choose>
    </where>
</select>

4.trim(set)

<!--public void updateEmp(Employee employee);  -->
<update id="updateEmp">
    
    <!-- Set标签的使用 -->
    <!--update tbl_employee 
	<set>
   		<if test="lastName!=null">
    		last_name=#{lastName},
   		</if>
        <if test="email!=null">
            email=#{email},
        </if>
        <if test="gender!=null">
            gender=#{gender}
        </if>
	</set>
    where id=#{id} -->
 		
	<!-- Trim:更新拼串 -->
	update tbl_employee 
    <trim prefix="set" suffixOverrides=",">
        <if test="lastName!=null">
            last_name=#{lastName},
        </if>
        <if test="email!=null">
            email=#{email},
        </if>
        <if test="gender!=null">
            gender=#{gender}
        </if>
    </trim>
    where id=#{id}  
</update>

5.foreach

<!--public List<Employee> getEmpsByConditionForeach(List<Integer> ids);  -->
<select id="getEmpsByConditionForeach" resultType="com.atguigu.mybatis.bean.Employee">
    select * from tbl_employee
    <!--
    collection:指定要遍历的集合:
     list类型的参数会特殊处理封装在map中,map的key就叫list
    item:将当前遍历出的元素赋值给指定的变量
    separator:每个元素之间的分隔符
    open:遍历出所有结果拼接一个开始的字符
    close:遍历出所有结果拼接一个结束的字符
    index:索引。遍历list的时候是index就是索引,item就是当前值
            遍历map的时候index表示的就是map的key,item就是map的值

    #{变量名}就能取出变量的值也就是当前遍历出的元素
     -->
    <foreach collection="ids" item="item_id" separator=","
             open="where id in(" close=")">
        #{item_id}
    </foreach>
</select>

批量保存(mysql)

<!-- 批量保存 -->
	 <!--public void addEmps(@Param("emps")List<Employee> emps);  -->
	 <!--MySQL下批量保存:可以foreach遍历   mysql支持values(),(),()语法-->
	<insert id="addEmps">
	 	insert into tbl_employee(
	 		<include refid="insertColumn"></include>
	 	) 
		values
		<foreach collection="emps" item="emp" separator=",">
			(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
		</foreach>
	 </insert><!--   -->
	 
	 <!-- 这种方式需要数据库连接属性allowMultiQueries=true;
	 	这种分号分隔多个sql可以用于其他的批量操作(删除,修改) -->
	 <!-- <insert id="addEmps">
	 	<foreach collection="emps" item="emp" separator=";">
	 		insert into tbl_employee(last_name,email,gender,d_id)
	 		values(#{emp.lastName},#{emp.email},#{emp.gender},#{emp.dept.id})
	 	</foreach>
	 </insert> -->

批量保存(oracle)

<!-- Oracle数据库批量保存: 
   Oracle不支持values(),(),()
   Oracle支持的批量方式
   1、多个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;
  2、利用中间表:
   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> -->

    <!-- oracle第二种批量方式  -->
    insert into employees(
    <!-- 引用外部定义的sql -->
    <include refid="insertColumn">
        <property name="testColomn" value="abc"/>
    </include>
    )
    <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>

6.parameter和_databaseId

7.bind

<!-- 两个内置参数:
   不只是方法传递过来的参数可以被用来判断,取值。。。
   mybatis默认还有两个内置参数:
   _parameter:代表整个参数
    单个参数:_parameter就是这个参数
    多个参数:参数会被封装为一个map;_parameter就是代表这个map

   _databaseId:如果配置了databaseIdProvider标签。
    _databaseId就是代表当前数据库的别名oracle
-->
	  
<!--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}
        </if>
    </if>
    <if test="_databaseId=='oracle'">
        select * from employees
        <if test="_parameter!=null">
            where last_name like #{_parameter.lastName}
        </if>
    </if>
</select>

8.抽取可重用的sql片段

<!-- 
    抽取可重用的sql片段。方便后面引用 
    1、sql抽取:将经常要查询的列名,或者插入用的列名抽取出来方便引用
    2、include来引用已经抽取的sql:
    3、include还可以自定义一些property,sql标签内部就能使用自定义的属性
      include-property:取值的正确方式${prop},
      #{不能使用这种方式}
-->
<sql id="insertColumn">
    <if test="_databaseId=='oracle'">
        employee_id,last_name,email
    </if>
    <if test="_databaseId=='mysql'">
        last_name,email,gender,d_id
    </if>
</sql>

六.MyBatis缓存机制

一级缓存二级缓存

  1. 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。

  2. 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

  3. 为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

1.一级缓存(本地缓存)

  • sqlSession级别的缓存。
  • 一级缓存是一直开启的;SqlSession级别的一个Map。
  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。

  • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;

  • 一级缓存失效情况(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询):

    1. sqlSession不同。

    2. sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)

    3. sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)

    4. sqlSession相同,手动清除了一级缓存(缓存清空:openSession.clearCache();)

一级缓存测试代码:

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);

        //xxxxx
        //1、sqlSession不同。
        //SqlSession openSession2 = sqlSessionFactory.openSession();
        //EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);

        //2、sqlSession相同,查询条件不同

        //3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
        //mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
        //System.out.println("数据添加成功");

        //4、sqlSession相同,手动清除了一级缓存(缓存清空)
        //openSession.clearCache();

        Employee emp02 = mapper.getEmpById(1);
        //Employee emp03 = mapper.getEmpById(3);
        System.out.println(emp02);
        //System.out.println(emp03);
        System.out.println(emp01==emp02);

        //openSession2.close();
    }finally{
        openSession.close();
    }
}

2.二级缓存(全局缓存)

  • 基于namespace级别的缓存:一个namespace对应一个二级缓存:

  • 工作机制:

    1. 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;

    2. 如果会话关闭;一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中的内容;

    3. sqlSession=EmployeeMapper>Employee

      DepartmentMapper===>Department

      不同namespace查出的数据会放在自己对应的缓存中(map)

  • 效果:数据会从二级缓存中获取

  • 查出的数据都会被默认先放在一级缓存中。

  • 只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中

  • 使用:

    1. 开启全局二级缓存配置:
    <setting name="cacheEnabled" value="true"/>
    
    1. 去mapper.xml中配置使用二级缓存:
    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
    <!--  
     eviction:缓存的回收策略:
      • LRU – 最近最少使用的:移除最长时间不被使用的对象。
      • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
      • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
      • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
      • 默认的是 LRU。
     flushInterval:缓存刷新间隔
      缓存多长时间清空一次,默认不清空,设置一个毫秒值
     readOnly:是否只读:
      true:只读;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
         mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快
      false:非只读:mybatis觉得获取的数据可能会被修改。
        mybatis会利用序列化&反序列的技术克隆一份新的数据给你。安全,速度慢
     size:缓存存放多少元素;
     type="":指定自定义缓存的全类名;
       实现Cache接口即可;
     -->
    
    1. 我们的POJO需要实现序列化接口
    public class Department implements Serializable{
        
    }
    

二级缓存测试代码:

@Test
public void testSecondLevelCache02() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
        //1、
        DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
        DepartmentMapper mapper2 = openSession2.getMapper(DepartmentMapper.class);

        Department deptById = mapper.getDeptById(1);
        System.out.println(deptById);
        openSession.close();

        Department deptById2 = mapper2.getDeptById(1);
        System.out.println(deptById2);
        openSession2.close();
        //第二次查询是从二级缓存中拿到的数据,并没有发送新的sql

    }finally{

    }
}

3.和缓存有关的设置/属性

  1. cacheEnabled=true:false:关闭缓存(二级缓存关闭)(一级缓存一直可用的)

  2. 每个select标签都有useCache=“true”:

    ​ false:不使用缓存(一级缓存依然使用,二级缓存不使用)

  3. 【每个增删改标签的:flushCache=“true”:(一级二级都会清除)】

    • 增删改执行完成后就会清楚缓存;

    • 测试:flushCache=“true”:一级缓存就清空了;二级也会被清除;

    • 查询标签:默认:flushCache=“false”:

      ​ 如果flushCache=true;每次查询之后都会清空缓存;缓存是没有被使用的;

  4. sqlSession.clearCache();只是清除当前session的一级缓存;

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

  6. 当在某一个作用域 (一级缓存Session/二级缓存Namespaces) 进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

4.第三方缓存整合

  1. 导入第三方缓存包;
  2. 导入与第三方缓存整合的适配包;官方地址
  3. mapper.xml中使用自定义缓存
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
<?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:\44\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(先进先出)
 -->

在其他mapper文件中引用缓存:

<!-- 引用缓存:namespace:指定和哪个名称空间下的缓存一样 -->
<cache-ref namespace="com.atguigu.mybatis.dao.EmployeeMapper"/>

七.MyBatis-Spring整合

  1. 查看不同MyBatis版本整合Spring时使用的适配包
  2. 下载整合适配包
  3. 官方整合示例,jpetstore

整合关键配置:

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

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

	<!-- 引入数据库的配置文件 -->
	<context:property-placeholder location="classpath:dbconfig.properties" />
	<!-- Spring用来控制业务逻辑。数据源、事务控制、aop -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="jdbcUrl" value="${jdbc.url}"></property>
		<property name="driverClass" value="${jdbc.driver}"></property>
		<property name="user" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>
	<!-- spring事务管理 -->
	<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!-- 开启基于注解的事务 -->
	<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
	
	<!-- 
	整合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:mybatis/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.atguigu.mybatis.dao"/>
	<!-- <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.atguigu.mybatis.dao"></property>
	</bean> -->
	
</beans>

八.MyBatis-逆向工程

1.概述

MyBatis Generator:简称MBG,是一个专门为MyBatis框架使用者定制的代码生成器,可以快速的根据表生成对应的映射文件,接口,以及bean类。支持基本的增删改查,以及QBC风格的条件查询。但是表连接、存储过程等这些复杂sql的定义需要我们手工编写。

官方文档地址

官方工程地址

2.MBG使用

2.1 使用步骤

  1. 编写MBG配置文件(详见2.3)

  2. 添加依赖

    <!--MBG-->
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.3.5</version>
    </dependency>
    
  3. 运行代码生成器生成代码

    @Test
    public void testMbg() throws Exception {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("mbg.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                                                                 callback, warnings);
        myBatisGenerator.generate(null);
    }
    
  4. 测试

    //简单版
    @Test
    public void testMyBatis3Simple() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            List<Employee> list = mapper.selectByExample(null);
            for (Employee employee : list) {
                System.out.println(employee.getId());
            }
        }finally{
            openSession.close();
        }
    }
    
    //豪华版
    @Test
    public void testMyBatis3() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        SqlSession openSession = sqlSessionFactory.openSession();
        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            //xxxExample就是封装查询条件的
            //1、查询所有
            //List<Employee> emps = mapper.selectByExample(null);
            //2、查询员工名字中有e字母的,和员工性别是1的
            //封装员工查询条件的example
            EmployeeExample example = new EmployeeExample();
            //创建一个Criteria,这个Criteria就是拼装查询条件
            //select id, last_name, email, gender, d_id from tbl_employee 
            //WHERE ( last_name like ? and gender = ? ) or email like "%e%"
            Criteria criteria = example.createCriteria();
            criteria.andLastNameLike("%e%");
            criteria.andGenderEqualTo("1");
    
            Criteria criteria2 = example.createCriteria();
            criteria2.andEmailLike("%e%");
            example.or(criteria2);
    
            List<Employee> list = mapper.selectByExample(example);
            for (Employee employee : list) {
                System.out.println(employee.getId());
            }
    
        }finally{
            openSession.close();
        }
    }
    

2.2 注意点

  • Context标签:
    • **targetRuntime=“MyBatis3“**可以生成带条件的增删改查
    • **targetRuntime=“MyBatis3Simple“**可以生成基本的增删改查
    • 如果再次生成,建议将之前生成的数据删除,避免xml向后追加内容出现的问题。

2.3 MBG配置文件

<?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:豪华版
	
	 -->
  <context id="DB2Tables" targetRuntime="MyBatis3">
  	<!-- jdbcConnection:指定如何连接到目标数据库 -->
    <jdbcConnection driverClass="com.mysql.jdbc.Driver"
        connectionURL="jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true"
        userId="root"
        password="123456">
    </jdbcConnection>

	<!--  -->
    <javaTypeResolver >
      <property name="forceBigDecimals" value="false" />
    </javaTypeResolver>

	<!-- javaModelGenerator:指定javaBean的生成策略 
	targetPackage="test.model":目标包名
	targetProject="\MBGTestProject\src":目标工程
	-->
    <javaModelGenerator targetPackage="com.atguigu.mybatis.bean" 
    		targetProject=".\src">
      <property name="enableSubPackages" value="true" />
      <property name="trimStrings" value="true" />
    </javaModelGenerator>

	<!-- sqlMapGenerator:sql映射生成策略: -->
    <sqlMapGenerator targetPackage="com.atguigu.mybatis.dao"  
    	targetProject=".\conf">
      <property name="enableSubPackages" value="true" />
    </sqlMapGenerator>

	<!-- javaClientGenerator:指定mapper接口所在的位置 -->
    <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.dao"  
    	targetProject=".\src">
      <property name="enableSubPackages" value="true" />
    </javaClientGenerator>

	<!-- 指定要逆向分析哪些表:根据表要创建javaBean -->
    <table tableName="tbl_dept" domainObjectName="Department"></table>
    <table tableName="tbl_employee" domainObjectName="Employee"></table>
  </context>
</generatorConfiguration>

九.MyBatis-工作原理

  1. 获取sqlSessionFactory对象:

    • 解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;

    • 注意:【MappedStatement】:代表一个增删改查的详细信息

  2. 获取sqlSession对象

    • 返回一个DefaultSQlSession对象,包含Executor和Configuration;

    • 这一步会创建Executor对象;

      获取接口的代理对象(MapperProxy)

  3. getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象

    • 代理对象里面包含了,DefaultSqlSession(Executor)
  4. 执行增删改查方法

总结:

  1. 根据配置文件(全局,sql映射)初始化出Configuration对象

  2. 创建一个DefaultSqlSession对象,他里面包含Configuration以及Executor(根据全局配置文件中的defaultExecutorType创建出对应的Executor)

  3. DefaultSqlSession.getMapper():拿到Mapper接口对应的MapperProxy;

  4. MapperProxy里面有(DefaultSqlSession);

  5. 执行增删改查方法:

    1. 调用DefaultSqlSession的增删改查(Executor);

    2. 会创建一个StatementHandler对象。(同时也会创建出ParameterHandler和ResultSetHandler)

    3. 调用StatementHandler预编译参数以及设置参数值;使用ParameterHandler来给sql设置参数

    4. 调用StatementHandler的增删改查方法;

    5. ResultSetHandler封装结果

      注意:四大对象每个创建的时候都有一个interceptorChain.pluginAll(parameterHandler);

十.MyBatis-插件开发

1.插件原理

在四大对象创建的时候:

  1. 每个创建出来的对象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler);
  2. 获取到所有的Interceptor(拦截器)(插件需要实现的接口);调用interceptor.plugin(target);返回target包装后的对象
  3. 插件机制,我们可以使用插件为目标对象创建一个代理对象;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)

2.插件编写

  1. 编写Interceptor的实现类

    package com.atguigu.mybatis.dao;
    
    import java.util.Properties;
    
    import org.apache.ibatis.executor.parameter.ParameterHandler;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.plugin.Interceptor;
    import org.apache.ibatis.plugin.Intercepts;
    import org.apache.ibatis.plugin.Invocation;
    import org.apache.ibatis.plugin.Plugin;
    import org.apache.ibatis.plugin.Signature;
    import org.apache.ibatis.reflection.MetaObject;
    import org.apache.ibatis.reflection.SystemMetaObject;
    
    /**
     * 完成插件签名:
     *		告诉MyBatis当前插件用来拦截哪个对象的哪个方法
     */
    @Intercepts({
    	@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
    })
    
    public class MyFirstPlugin implements Interceptor{
    
    	/**
    	 * intercept:拦截:
    	 * 		拦截目标对象的目标方法的执行;
    	 */
    	@Override
    	public Object intercept(Invocation invocation) throws Throwable {
    		// TODO Auto-generated method stub
    		System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
    		//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询11号员工
    		Object target = invocation.getTarget();
    		System.out.println("当前拦截到的对象:"+target);
    		//拿到:StatementHandler==>ParameterHandler===>parameterObject
    		//拿到target的元数据
    		MetaObject metaObject = SystemMetaObject.forObject(target);
    		Object value = metaObject.getValue("parameterHandler.parameterObject");
    		System.out.println("sql语句用的参数是:"+value);
    		//修改完sql语句要用的参数
    		metaObject.setValue("parameterHandler.parameterObject", 11);
    		//执行目标方法
    		Object proceed = invocation.proceed();
    		//返回执行后的返回值
    		return proceed;
    	}
    
    	/**
    	 * plugin:
    	 * 		包装目标对象的:包装:为目标对象创建一个代理对象
    	 */
    	@Override
    	public Object plugin(Object target) {
    		// TODO Auto-generated method stub
    		//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
    		System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
    		Object wrap = Plugin.wrap(target, this);
    		//返回为当前target创建的动态代理
    		return wrap;
    	}
    
    	/**
    	 * setProperties:
    	 * 		将插件注册时 的property属性设置进来
    	 */
    	@Override
    	public void setProperties(Properties properties) {
    		// TODO Auto-generated method stub
    		System.out.println("插件配置的信息:"+properties);
    	}
    
    }
    
    
  2. 使用@Intercepts注解完成插件签名

  3. 将写好的插件注册到全局配置文件中

    <!--plugins:注册插件  -->
    <plugins>
        <plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </plugin>
        <plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin">
        </plugin>
    </plugins>
    

十一.MyBatis-实用场景

1. PageHelper插件进行分页

官方文档

使用步骤:

  1. 导入相关包pagehelper-x.x.x.jar 和 jsqlparser-0.9.5.jar

  2. 在MyBatis全局配置文件中配置分页插件

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
    
  3. 使用PageHelper提供的方法进行分页

    @Test
    public void test01() throws IOException {
        // 1、获取sqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
        // 2、获取sqlSession对象
        SqlSession openSession = sqlSessionFactory.openSession();
        try {
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            Page<Object> page = PageHelper.startPage(1, 5);
    
            List<Employee> emps = mapper.getEmps();
            //传入要连续显示多少页
            PageInfo<Employee> info = new PageInfo<>(emps, 5);
            for (Employee employee : emps) {
                System.out.println(employee);
            }
            /*System.out.println("当前页码:"+page.getPageNum());
    			System.out.println("总记录数:"+page.getTotal());
    			System.out.println("每页的记录数:"+page.getPageSize());
    			System.out.println("总页码:"+page.getPages());*/
            ///xxx
            System.out.println("当前页码:"+info.getPageNum());
            System.out.println("总记录数:"+info.getTotal());
            System.out.println("每页的记录数:"+info.getPageSize());
            System.out.println("总页码:"+info.getPages());
            System.out.println("是否第一页:"+info.isIsFirstPage());
            System.out.println("连续显示的页码:");
            int[] nums = info.getNavigatepageNums();
            for (int i = 0; i < nums.length; i++) {
                System.out.println(nums[i]);
            }
    
    
            //xxxx
        } finally {
            openSession.close();
        }
    }
    
  4. 可以使用更强大的PageInfo封装返回结果

2. 批量操作

2.1 操作说明

默认的 openSession() 方法没有参数,它会创建有如下特性的

  • 会开启一个事务(也就是不自动提交)

  • 连接对象会从由活动环境配置的数据源实例得到。

  • 事务隔离级别将会使用驱动或数据源的默认设置。

  • 预处理语句不会被复用,也不会批量处理更新。

openSession 方法的 ExecutorType 类型的参数,枚举类型:

  • ExecutorType.SIMPLE: 这个执行器类型不做特殊的事情(这是默认装配的)。它为每个语句的执行创建一个新的预处理语句
  • ExecutorType.REUSE: 这个执行器类型会复用预处理语句
  • ExecutorType.BATCH: 这个执行器会批量执行所有更新语句
@Test
public void testBatch() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

    //可以执行批量操作的sqlSession
    SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    long start = System.currentTimeMillis();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        for (int i = 0; i < 10000; i++) {
            mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
        }
        openSession.commit();
        long end = System.currentTimeMillis();
        //批量:(预编译sql一次==>设置参数===>10000次===>执行(1次))
        //Parameters: 616c1(String), b(String), 1(String)==>4598
        //非批量:(预编译sql=设置参数=执行)==》10000    10200
        System.out.println("执行时长:"+(end-start));
    }finally{
        openSession.close();
    }
}

2.2 与Spring整合

<!--配置一个可以进行批量执行的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>
@Autowired
private SqlSession sqlSession;

public List<Employee> getEmps(){
    EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
    return mapper.getEmps();
}

2.3 注意

  1. 批量操作是在session.commit()以后才发送sql语句给数据库进行执行的
  2. 如果我们想让其提前执行,以方便后续可能的查询操作获取数据,我们可以使用sqlSession.flushStatements()方法,让其直接冲刷到数据库进行执行

3. 存储过程(oracle分页)

3.1 在数据库创建存储过程

create or replace procedure
	hello_test(
    	p_start in int,p_end in int,p_count out int,p_emps out sys_refcursor
    )as
begin
	select count(*) into p_count from employees;
	open p_emps for
		select * from (select rownum rn,e.* from employees e where rownum<=p_end) where rn>=p_start;
end hello_test;

3.2 创建分页类

package com.atguigu.mybatis.bean;

import java.util.List;

/**
 * 封装分页查询数据
 * @author lfy
 *
 */
public class OraclePage {
	
	private int start;
	private int end;
	private int count;
	private List<Employee> emps;
	
	public int getStart() {
		return start;
	}
	public void setStart(int start) {
		this.start = start;
	}
	public int getEnd() {
		return end;
	}
	public void setEnd(int end) {
		this.end = end;
	}
	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
	public List<Employee> getEmps() {
		return emps;
	}
	public void setEmps(List<Employee> emps) {
		this.emps = emps;
	}
}

3.3 在mapper.xml文件中调用存储过程

<!-- public void getPageByProcedure(); 
 1、使用select标签定义调用存储过程
 2、statementType="CALLABLE":表示要调用存储过程
 3、{call procedure_name(params)}
 -->
<select id="getPageByProcedure" statementType="CALLABLE" databaseId="oracle">
    {call hello_test(
    #{start,mode=IN,jdbcType=INTEGER},
    #{end,mode=IN,jdbcType=INTEGER},
    #{count,mode=OUT,jdbcType=INTEGER},
    #{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=PageEmp}
    )}
</select>
<resultMap type="com.atguigu.mybatis.bean.Employee" id="PageEmp">
    <id column="EMPLOYEE_ID" property="id"/>
    <result column="LAST_NAME" property="email"/>
    <result column="EMAIL" property="email"/>
</resultMap>

3.4 测试代码

/**
* oracle分页:
* 		借助rownum:行号;子查询;
* 存储过程包装分页逻辑 
*/
@Test
public void testProcedure() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        OraclePage page = new OraclePage();
        page.setStart(1);
        page.setEnd(5);
        mapper.getPageByProcedure(page);

        System.out.println("总记录数:"+page.getCount());
        System.out.println("查出的数据:"+page.getEmps().size());
        System.out.println("查出的数据:"+page.getEmps());
    }finally{
        openSession.close();
    }
}

4. typeHandler处理枚举(用户登陆状态码)

4.1 枚举类

package com.atguigu.mybatis.bean;

import com.sun.org.apache.bcel.internal.generic.RETURN;

/**
 * 希望数据库保存的是100,200这些状态码,而不是默认0,1或者枚举的名
 */
public enum EmpStatus {
	LOGIN(100,"用户登录"),LOGOUT(200,"用户登出"),REMOVE(300,"用户不存在");
	
	private Integer code;
	private String msg;
    
	private EmpStatus(Integer code,String msg){
		this.code = code;
		this.msg = msg;
	}
	public Integer getCode() {
		return code;
	}
	
	public void setCode(Integer code) {
		this.code = code;
	}
	public String getMsg() {
		return msg;
	}
	public void setMsg(String msg) {
		this.msg = msg;
	}
	
	//按照状态码返回枚举对象
	public static EmpStatus getEmpStatusByCode(Integer code){
		switch (code) {
			case 100:
				return LOGIN;
			case 200:
				return LOGOUT;	
			case 300:
				return REMOVE;
			default:
				return LOGOUT;
		}
	}
}

4.2 自定义枚举类的类型处理器

package com.atguigu.mybatis.typehandler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandler;

import com.atguigu.mybatis.bean.EmpStatus;

/**
 * 1、实现TypeHandler接口。或者继承BaseTypeHandler
 */
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
	/**
	 * 定义当前数据如何保存到数据库中
	 */
	@Override
	public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
			JdbcType jdbcType) throws SQLException {
		// TODO Auto-generated method stub
		System.out.println("要保存的状态码:"+parameter.getCode());
		ps.setString(i, parameter.getCode().toString());
	}

	@Override
	public EmpStatus getResult(ResultSet rs, String columnName)
			throws SQLException {
		// TODO Auto-generated method stub
		//需要根据从数据库中拿到的枚举的状态码返回一个枚举对象
		int code = rs.getInt(columnName);
		System.out.println("从数据库中获取的状态码:"+code);
		EmpStatus status = EmpStatus.getEmpStatusByCode(code);
		return status;
	}

	@Override
	public EmpStatus getResult(ResultSet rs, int columnIndex)
			throws SQLException {
		// TODO Auto-generated method stub
		int code = rs.getInt(columnIndex);
		System.out.println("从数据库中获取的状态码:"+code);
		EmpStatus status = EmpStatus.getEmpStatusByCode(code);
		return status;
	}

	@Override
	public EmpStatus getResult(CallableStatement cs, int columnIndex)
			throws SQLException {
		// TODO Auto-generated method stub
		int code = cs.getInt(columnIndex);
		System.out.println("从数据库中获取的状态码:"+code);
		EmpStatus status = EmpStatus.getEmpStatusByCode(code);
		return status;
	}

}

4.3 mybatis配置文件引入自定义类型处理器

<typeHandlers>
    <!--1、配置我们自定义的TypeHandler  -->
    <typeHandler handler="com.atguigu.mybatis.typehandler.MyEnumEmpStatusTypeHandler" javaType="com.atguigu.mybatis.bean.EmpStatus"/>
    <!--2、也可以在处理某个字段的时候告诉MyBatis用什么类型处理器
    保存:#{empStatus,typeHandler=xxxx}
    查询:
     <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmp">
       <id column="id" property="id"/>
       <result column="empStatus" property="empStatus" typeHandler=""/>
      </resultMap>
    注意:如果在参数位置修改TypeHandler,应该保证保存数据和查询数据用的TypeHandler是一样的。
    -->
</typeHandlers>

4.4 测试代码

@Test
public void testEnumUse(){
    EmpStatus login = EmpStatus.LOGIN;
    System.out.println("枚举的索引:"+login.ordinal());
    System.out.println("枚举的名字:"+login.name());

    System.out.println("枚举的状态码:"+login.getCode());
    System.out.println("枚举的提示消息:"+login.getMsg());
}

/**
* 默认mybatis在处理枚举对象的时候保存的是枚举的名字:EnumTypeHandler
* 改变使用:EnumOrdinalTypeHandler:
* @throws IOException
*/
@Test
public void testEnum() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee employee = new Employee("test_enum", "enum@atguigu.com","1");
        //mapper.addEmp(employee);
        //System.out.println("保存成功"+employee.getId());
        //openSession.commit();
        Employee empById = mapper.getEmpById(30026);
        System.out.println(empById.getEmpStatus());
    }finally{
        openSession.close();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值