mybatis扩展-查询结果排序的两种方式

1.使用PageHelper排序(Pagehelper的版本需在5.1.2及以上)

<!– https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper –>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>

PageHelper.startPage(pageNum , pageSize);PageHelper.orderBy(“A B”);
其中A为排序依据的字段名,B为排序规律,desc为降序,asc为升序

或者一步到位

String orderBy=”字段名 排序规律”;

PageHelper.startPage(pageNum, pageSize, orderBy);

2.使用Mybatis排序

XXXExample example = new XXXExample();

example.setOrderByClause(“字段名1 ASC/DESC,字段名2 ASC/DESC,…”);
———————

来自:https://blog.csdn.net/kalnon/article/details/79559627

mybatis踩坑-自生成mapper映射文件的MBG的路径问题

Mybatis Generator代码不报错,但是没有生成文件

比如:之前是 targetProject=”.\src”  适用于windows,在当前目录的src创建文件。
targetProject=”./src”  适用于 linux ,在当前目录的 src 创建文件。
【Windows 无法识别  ./     同理 linux 也无法识别  .\】

<javaClientGenerator type="XMLMAPPER"
	targetPackage="com.mybatis.mapper"
	targetProject=".\src">
	<!-- enableSubPackages:是否让schema作为包的后缀 -->
	<property name="enableSubPackages" value="false" />
</javaClientGenerator>

MBG 配置目录的时候 targetProject=“./src”  需要知道 是windows环境还是mac环境。主要是 目录设置问题。windows环境:.\    mac环境:./ 

当然目录可以去掉前面./或.\  直接开始写文件夹就行了,这样子 mac和windows都可以运行了。

 

mybatis踩坑-IncompleteElementException

org.apache.ibatis.builder.IncompleteElementException: Could not find SQL statement to include with refid ‘com.ssm.crud.mapper.EmployeeMapper.WithDept_Column_List

at org.apache.ibatis.builder.xml.XMLIncludeTransformer.findSqlFragment(XMLIncludeTransformer.java:90)

这句话报错的意思是,sql映射文件中:没有定义说明 WithDept_Column_List语句的含义。比如在下面的配置文件中:SQL标签的语句一开始没有写,导致下面的select语句,无法解析导致报错了。

<!--  ****** 自定义的 带部门信息 的两种查询  ******  -->
<sql id="WithDept_Column_List">
  e.emp_id, e.emp_name, e.gender, e.email, e.d_id,d.dept_id,d.dept_name
</sql>

  <select id="selectByPrimaryKeyWithDept" resultMap="WithDeptResultMap">
   	select 
    <include refid="WithDept_Column_List" />
    FROM tbl_emp e
	left join tbl_dept d on e.`d_id`=d.`dept_id`
    where emp_id = #{empId,jdbcType=INTEGER}
  </select>

 

mybatis插件-分页插件框架

一、导入jar包

1、jsqlparser.jar
2、pagehelper.jar

二、启用插件

在全局配置文件中:mybatis-config.xml中,添加

 <!-- 启用自定义插件拦截器 -->
 <plugins>
	<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

三、使用教程

直接在需要的地方,调用 PageHelper就行了。
PageHelper,Page,PageInfo,都是分页插件自带的对象。

@Test
public void test_Page() throws IOException {
	// 1、获取sqlSessionFactory对象
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
	// 2、获取sqlSession对象
	SqlSession openSession = sqlSessionFactory.openSession();
	try {
		// 3、获取接口的实现类对象
		//会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			
		Page<Object> page  =  PageHelper.startPage(1,5);
		List<Employee> emps = mapper.getEmps();
		/*System.out.println("当前页码:"+page.getPageNum());
		System.out.println("总记录数:"+page.getTotal());
		System.out.println("每页的记录数:"+page.getPageSize());
		System.out.println("总页码:"+page.getPages());*/
			
		//传入要连续显示多少页
		PageInfo<Employee> info = new PageInfo<>(emps, 5);
		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]);
		}
			
		
		for (Employee employee : emps) {
			System.out.println(employee);
		}
			
			
	} finally {
		openSession.close();
	}

}

 

 

mybatis扩展-自定义TypeHandler类型

自定义类型的Typehandler

一、枚举类型的Typehandler

mybatis 在 处理 枚举类型 的读取和保存时,默认提供 了 两种方案:
1、mybatis  默认使用EnumTypeHandler  在处理枚举对象的时候保存的是枚举的名字。
2、 也可以改用:EnumOrdinalTypeHandler:【读取和保存都是 》枚举的索引】

二、如何改用Typehandler

可以在mybatis-config.xml中配置,也可以在SQL映射文件中:

<typeHandlers>
	<!--1、配置我们自定义的TypeHandler  -->
	<typeHandler handler="com.mybatis.typehandler.MyEnumEmpStatusTypeHandler" javaType="com.mybatis.typehandler.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>

三、自定义Typehandler

这里用自定义枚举类型处理器举例:

1、自定义枚举类型:

EmpStatus.java

package com.mybatis.typehandler;

/**
 * 希望数据库保存的是100,200这些状态码,而不是默认0,1或者枚举的名
 * @author lfy
 *
 */
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;
		}
	}
	
	
}

2、自定义typeHandler

EnumEmpStatusTypeHandler.java

package com.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;



/**
 * 1、实现TypeHandler接口。或者继承BaseTypeHandler
 * @author lfy
 *
 */
public class EnumEmpStatusTypeHandler 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;
	}

}

3、在全局配置文件中,启用自定义类型

配置mybatis-config.xml,启用 EnumEmpStatusTypeHandler

<?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>

<typeHandlers>
		<!--1、配置我们自定义的TypeHandler  -->
		<typeHandler handler="com.mybatis.typehandler.EnumEmpStatusTypeHandler" javaType="com.mybatis.typehandler.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>

	<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="Kitty521!" />
			</dataSource>
		</environment>
	</environments>
	
		<!-- 5、databaseIdProvider:支持多数据库厂商的;
		 type="DB_VENDOR":VendorDatabaseIdProvider
		 	作用就是得到数据库厂商的标识(驱动getDatabaseProductName()),mybatis就能根据数据库厂商标识来执行不同的sql;
		 	MySQL,Oracle,SQL Server,xxxx
	  -->
	<databaseIdProvider type="DB_VENDOR">
		<!-- 为不同的数据库厂商起别名 -->
		<property name="MySQL" value="mysql"/>
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
	</databaseIdProvider>
	
	<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 
	如果 数据库全局文件 和 子配置文件 不在同一个目录 ,就需要 /目录/目录/.../EmployeeMapper_old.xml
	-->
	<mappers>
	    <!-- 新方法操作mybatis 需要 的配置文件 -->
		<mapper resource="EmployeeMapper.xml" />
	</mappers>
</configuration>

四、测试自定义typeHandler

1、javabean对象

package com.mybatis.bean;

import com.mybatis.typehandler.EmpStatus;

public class Employee {
	private Integer id;
	private String lastName;
	private String email;
	private String gender;
	//员工状态
	private EmpStatus empStatus=EmpStatus.LOGOUT;
		
	public EmpStatus getEmpStatus() {
		return empStatus;
	}

	public void setEmpStatus(EmpStatus empStatus) {
		this.empStatus = empStatus;
	}

	public int getId() {
		return id;
	}

	public void setId(int 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;
	}
	
	public Employee(){
		super();
	}
	
	public Employee(String lastName, String email, String gender) {
		super();
		this.lastName = lastName;
		this.email = email;
		this.gender = gender;
	}

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

}

2、mapper接口对象

package com.mybatis.mapper;

import java.util.List;

import com.mybatis.bean.Employee;
import com.mybatis.bean.ProcPage;

public interface EmployeeMapper {

	public Employee getEmpById(Integer id);

	public Long addEmp(Employee employee);

}

3、SQL映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.mapper.EmployeeMapper">
	
	<!-- 
namespace:名称空间;指定为接口的全类名
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值

public Employee getEmpById(Integer id);
 -->
 	
	<select id="getEmpById" resultType="com.mybatis.bean.Employee">
		select id,last_name lastName,email,gender,empStatus from tbl_employee where id = #{id}
	</select>
	
	
	<!--public Long addEmp(Employee employee);  -->
	<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
		insert into tbl_employee(last_name,email,gender,empStatus) 
		values(#{lastName},#{email},#{gender},#{empStatus})
	</insert>
</mapper>

4、Junit单元测试

注意:要分两步操作,第一步先插入数据,观察数据库中枚举的保存形式,第二步再读取数据,观察枚举的读取形式。

package com.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.mybatis.bean.Employee;
import com.mybatis.bean.ProcPage;
import com.mybatis.mapper.EmployeeMapper;
import com.mybatis.typehandler.EmpStatus;


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

public class MybatisTest {
	
	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}
	
	
	@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", "[email protected]","1");
//			mapper.addEmp(employee);
//			System.out.println("保存成功"+employee.getId());
//			openSession.commit();

//  第二遍:当数据库执行插入操作后,观察 插入是是 那一条 ID,然后再读取出来 ,查看 枚举类型
//			Employee empById = mapper.getEmpById(11);
//			System.out.println(empById.getEmpStatus());
		
                 }finally{
			openSession.close();
		}
	}
	
}

 

mybatis扩展-存储过程

一、存储过程调用关键

1、SQL映射文件中,使用select标签定义调用存储过程
(1)statementType=”CALLABLE”:表示要调用存储过程
(2){call procedure_name(params)}
2、编辑存储过程对象,用于保存存储过程的输入参数和输出参数

二、存储过程调用-mysql

1、搭建数据库

id      last_name    gender      email
1	  mike	       0         [email protected]	
2	  book	       0  	[email protected]	
3	  tom	       1        [email protected]	
4	  jerry	       1	[email protected]	
5	  hhee	       1	[email protected]	
6	  jerry4       1	[email protected]	
7	  smith0x1     1	[email protected]	
8	  mas	       1	[email protected]	
9	  smith0x1     1	[email protected]	
10	  allen0x1     0	[email protected]

2、创建存储过程

USE `mybatis`;
DROP PROCEDURE IF EXISTS proc_employee;
DELIMITER //
CREATE PROCEDURE proc_employee(IN p_start INT, IN p_end INT,OUT p_count INT)
BEGIN
  SELECT COUNT(*) INTO p_count FROM `tbl_employee`;
  SELECT * FROM ( SELECT a.*  FROM `tbl_employee` a  WHERE a.`id` < p_end)  b
  WHERE b.`id` >p_start;
END//
DELIMITER ;

特别注意点:

开头部分:DELIMITER //【这两斜杠前面有空格,需要注意】
结尾部分:DELIMITER ;【分号前面也有个空格,需要注意】

在定义过程时,使用DELIMITER // 命令将语句的结束符号从分号 ; 
临时改为//,使得过程体中使用的分号被直接传递到服务器,而不会被客户端(如mysql)解释。

3、mysql客户端测试端调用存储过程

set @p_count=1;
CALL proc_employee(1,8,@p_count); 
select @p_count;

4、编辑全局配置文件

<?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="Cool123!" />
			</dataSource>
		</environment>
	</environments>
	
		<!-- 5、databaseIdProvider:支持多数据库厂商的;
		 type="DB_VENDOR":VendorDatabaseIdProvider
		 	作用就是得到数据库厂商的标识(驱动getDatabaseProductName()),mybatis就能根据数据库厂商标识来执行不同的sql;
		 	MySQL,Oracle,SQL Server,xxxx
	  -->
	<databaseIdProvider type="DB_VENDOR">
		<!-- 为不同的数据库厂商起别名 -->
		<property name="MySQL" value="mysql"/>
		<property name="Oracle" value="oracle"/>
		<property name="SQL Server" value="sqlserver"/>
	</databaseIdProvider>
	
	<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 
	如果 数据库全局文件 和 子配置文件 不在同一个目录 ,就需要 /目录/目录/.../EmployeeMapper_old.xml
	-->
	<mappers>
	    <!-- 新方法操作mybatis 需要 的配置文件 -->
		<mapper resource="EmployeeMapper.xml" />
	</mappers>
</configuration>

5、编辑javabean

Employee.java

package com.mybatis.bean;

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

	public int getId() {
		return id;
	}

	public void setId(int 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;
	}
	
	public Employee(){
		super();
	}
	
	public Employee(String lastName, String email, String gender) {
		super();
		this.lastName = lastName;
		this.email = email;
		this.gender = gender;
	}

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

}

存储过程的bean文件  ProcPage.java

package com.mybatis.bean;

import java.util.List;

/**
 * 封装分页查询数据
 * @author lfy
 *
 */
public class ProcPage {
	
	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;
	}
	
	

}

6、配置mapper接口

EmployeeMapper.java

package com.mybatis.mapper;

import java.util.List;

import com.mybatis.bean.Employee;
import com.mybatis.bean.ProcPage;

public interface EmployeeMapper {
	
	public List<Employee> getPageByProcedure(ProcPage page);
	
}

7、配置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="com.mybatis.mapper.EmployeeMapper">
	

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

8、Junit单元测试

package com.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.mybatis.bean.ProcPage;
import com.mybatis.mapper.EmployeeMapper;


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

public class MybatisTest {
	
	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "mybatis-config.xml";
		InputStream inputStream = Resources.getResourceAsStream(resource);
		return new SqlSessionFactoryBuilder().build(inputStream);
	}
	
	/**
	 * mysql分页:
	 * 存储过程包装分页逻辑
	 * @throws IOException 
	 */
	@Test
	public void testProcedure() throws IOException{
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		try{
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			ProcPage page = new ProcPage();
			page.setStart(5);
			page.setEnd(8);
			page.setEmps(mapper.getPageByProcedure(page));
			
			System.out.println("总记录数:"+page.getCount());
			System.out.println("查出的数据:"+page.getEmps().size());
			System.out.println("查出的数据:"+page.getEmps());
		}finally{
			openSession.close();
		}
		
	}
}

三、存储过程调用流程-oracle

基本和mysql相同,部分不相同。【因为存储过程不一样,存储过程对应有四个参数,导致后面的操作稍微不同】

1、数据库存储过程:

create or replace procedure
proc_employee(p_start in int,
              p_end in int,
              p_count out int,
              ref_cur out sys_refcursor) AS
BEGIN 
   select count(*) into p_count from tbl_employee;
   open ref_cur for
        select * from (select e.*,rownum as rn) from tbl_employee e where rownum <p_end)
        where rn >p_start;
END proc_employee;

2、mapper接口文件

package com.mybatis.mapper;

import java.util.List;

import com.mybatis.bean.Employee;
import com.mybatis.bean.ProcPage;

public interface EmployeeMapper {
	
	public void getPageByProcedure(ProcPage page);
	
}

3、SQL映射文件

<mapper namespace="com.mybatis.mapper.EmployeeMapper">

	<!-- 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.mybatis.bean.Employee" id="PageEmp">
		<id column="EMPLOYEE_ID" property="id"/>
		<result column="LAST_NAME" property="email"/>
		<result column="EMAIL" property="email"/>
	</resultMap>
	
</mapper>

4、Junit单元测试

/**
 * oracle分页:
 * 		借助rownum:行号;子查询;
 * 存储过程包装分页逻辑
 * @throws IOException 
 */
@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();
	}
		
}

 

mybatis扩展-批量操作

批量操作的关键,就是 opensession 中的 带有 ExecutorType.BATCH,除此之外,和一般的操作没有区别。

一、mybatis独立环境中

@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();
	}
		
}

上面的代码如果是:下面这样的,那么sqlsession 就是非批量的,执行速度就慢了。

SqlSession openSession = sqlSessionFactory.openSession();

二、mybatis与spring整合【即SSM框架】环境

需要在spring-ioc容器配置文件(一般取名叫: applicationContext.xml)中添加带有ExecutorType.BATCH的 sqlSession对象。

<!--配置一个可以进行批量执行的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>

然后就可以在service中,自动注入 sqlSession

举例:EmployeeService.java

package com.test.springmvc.service;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.test.springmvc.bean.Employee;
import com.test.springmvc.dao.EmployeeMapper;

@Service
public class EmployeeService {
	
	//employeeMapper 是靠 applicationContext.xml的IOC容器,自动创建并赋值到这里的。
	@Autowired
	private EmployeeMapper employeeMapper;

    public List<Employee> getEmps(){
		
		return employeeMapper.getEmps();
	}
	
	//sqlSession 是为了从 applicationContext.xml的IOC容器中 获取批量处理的 sqlSession 对象,并赋值给自己
	@Autowired
	private SqlSession sqlSession;
	
	public List<Employee> getEmpsBatch(){
		EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);		
		return mapper.getEmps();
	}
	
	
}

这样 控制器 controller 的写法可以写成这样了:

package com.test.springmvc.controller;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.test.springmvc.bean.Employee;
import com.test.springmvc.service.EmployeeService;

@Controller
public class EmployeeController {
	
	@Autowired
	EmployeeService employeeService;
	
	@RequestMapping("/getemps")
	public String emps(Map<String,Object> map){

                //没有采用批量处理的方法
                //List<Employee> emps = employeeService.getEmps();
		
                //采用批量处理的方法
                List<Employee> emps = employeeService.getEmpsBatch();
		map.put("allEmps", emps);
		return "list";
	}

}

 

mybatis插件-原理及开发

一、插件原理

1、MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。

2、MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。

3、默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
•Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
•ParameterHandler(getParameterObject, setParameters)
•ResultSetHandler(handleResultSets, handleOutputParameters)
•StatementHandler(prepare, parameterize, batch, update, query)

/**
 * 插件原理
 * 在四大对象创建的时候
 * 1、每个创建出来的对象不是直接返回的,而是
 *  xxx = interceptorChain.pluginAll(parameterHandler);
    
   具体方法如下:其实就是遍历调用了每个插件的plugin(target)方法,
      如果插件的方法签名和目标对象一致,就返回目标对象的代理对象,否则就返回目标对象
	public Object pluginAll(Object target) {
	    for (Interceptor interceptor : interceptors) {
	      target = interceptor.plugin(target);
	    }
	    return target;
	  }
      

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

接口方法:
•Intercept:拦截目标方法执行
•plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法
•setProperties:注入插件配置时设置的属性

二、插件编写过程

1、编写Interceptor的实现类

package com.mybatis.plugin;

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号员工,实际从数据库查询3号员工
		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注解完成插件签名

已经在前面的MyFirstPlugin.java添加了,注解签名。

@Intercepts(
{
	@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})

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

在mybatis-config.xml中,添加插件配置项:

<!--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的配置的插件顺序依次加载。
一个目标对象如果有多个插件,会在目标的代理对象上继续创建代理。

mybatis运行原理

一、基本介绍

/**
 * 1、获取sqlSessionFactory对象:
 * 		解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
 * 		注意:【MappedStatement】:代表一个增删改查的详细信息
 * 
 * 2、获取sqlSession对象
 * 		返回一个DefaultSQlSession对象,包含Executor和Configuration;
 * 		这一步会创建Executor对象;
 * 
 * 3、获取接口的代理对象(MapperProxy)
 * 		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);
 *

二、运行流程图