一、简单例子
说明:本例基于mybatis-3.2.6。
1.新建web项目mybatis,引入jar包mybatis-3.2.6.jar。
2.新建java类com.hanjun.entity.User(数据库中建立与些类对应的t_user表)
private int id;
private String userName;
private String userAge;
private String userAddress;
3.在User类同包下新建User.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.hanjun.entity.User">
<select id="selectUserByID" parameterType="int" resultType="User">
select * from t_user where id = #{id}
</select>
</mapper>
说明:namespace为自定义的包名,包名.select的id确定唯一sql。parameterType可以不要。
4.在src下新建mybatis配置文件Configuration.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>
<typeAliases>
<typeAlias alias="User" type="com.hanjun.entity.User" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:XE" />
<property name="username" value="zhanghanjun" />
<property name="password" value="pwd123" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/hanjun/entity/User.xml" />
</mappers>
</configuration>
说明:User.xml中的resultType对应此处的alias。如果不声明alias,修改User.xml中的resultType为com.hanjun.entity.User也可
5.测试:
public static void main(String[] args) {
Reader reader = null;
try {
reader = Resources.getResourceAsReader("Configuration.xml");
} catch (IOException e) {
e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
try {
User user = (User) session.selectOne("com.hanjun.entity.User.selectUserByID", 1);
System.out.println(user.getUserAddress());
System.out.println(user.getUserName());
} finally {
session.close();
}
}
6.优化,新建com.hanjun.dao.IUserDao,并修改User.xml中mapper的namespace为com.hanjun.dao.IUserDao
public interface IUserDao {
// 方法名与User.xml中select的id保持一致
public User selectUserByID(int id);
}
修改测试方法,可以直接使用IUserDao调用数据库了
SqlSession session = sqlSessionFactory.openSession();
IUserDao userDao = session.getMapper(IUserDao.class);
try {
User user = userDao.selectUserByID(1);
System.out.println(user.getUserAddress());
System.out.println(user.getUserName());
} finally {
session.close();
}
二、增删改查
1.增加数据。User.xml中增加
<insert id="addUser" parameterType="User">
insert into t_user(id,userName,userAge,userAddress)
values(q_user.nextval,#{userName},#{userAge},#{userAddress})
</insert>
说明:如果是MySql等自增主键,insert的属性 seGeneratedKeys设置为"true"表明要MyBatis获取由数据库自动生成的主键;keyProperty="id"指定把获取到的主键值注入到User的id属性
IUserDao中增加
public void addUser(User user);
测试
User user = new User();
user.setUserAge("26");
user.setUserName("luowei");
user.setUserAddress("湖南");
userDao.addUser(user);
session.commit();
说明:增删改操作必须执行session.commit()方法。
2.修改数据。User.xml中增加
<update id="updateUser" parameterType="User" >
update t_user set userName=#{userName},userAge=#{userAge},userAddress=#{userAddress} where id=#{id}
</update>
IUserDao中增加
public void updateUser(User user);
测试
User user = userDao.selectUserByID(3);
user.setUserAge("27");
userDao.updateUser(user);
3.删除数据。User.xml中增加
<delete id="deleteUser" parameterType="int">
delete from t_user where id=#{id}
</delete>
IUserDao中增加
public void deleteUser(User user);
测试
User user = userDao.selectUserByID(3);
user.setUserAge("27");
userDao.deleteUser(user);
4.List查询。User.xml中增加
<select id="selectUsers" parameterType="string" resultType="com.hanjun.entity.User">
select * from t_user where userName like ('%' || #{userName} || '%')
</select>
IUserDao中增加
public List<User> selectUsers(String userName);
测试
List<User> users = userDao.selectUsers("luowe");
for(User user:users){
System.out.println(user.getId()+":"+user.getUserName()+":"+user.getUserAddress());
}
5.多对一查询。新建表t_article和实体类com.hanjun.entity.Article
private int id;
private User user;
private String title;
private String content;
新建com.hanjun.entity.IArticleDao
public interface IArticleDao {
public List<Article> findArticles(Article article);
}
修改Configuration.xml
<mappers>
<mapper resource="com/hanjun/entity/User.xml" />
<mapper resource="com/hanjun/entity/Article.xml" />
</mappers>
新建Article.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.hanjun.dao.IArticleDao">
<resultMap id="resultMapUser" type="com.hanjun.entity.User">
<id column="userId" property="id" />
<result column="userName" property="userName" />
<result column="userAge" property="userAge" />
<result column="userAddress" property="userAddress" />
</resultMap>
<resultMap id="resultMapArticle" type="com.hanjun.entity.Article" autoMapping="true">
<association property="user" javaType="User" resultMap="resultMapUser" />
</resultMap>
<select id="findArticles" parameterType="User" resultMap="resultMapArticle">
select u.id userId, u.userName, u.userAddress, a.id, a.title, a.content
from t_user u, t_article a
where u.id = a.userid
and u.id = #{user.id}
</select>
</mapper>
说明:autoMapping设置为true,sql列名和实体类属性名一致时自动注入,可省略配置一些association
说明:resultMapArticle引用resultMapUser也可写为:
<association property="user" javaType="User" autoMapping="true">
<id property="id" column="userId"/>
</association>
测试
IArticleDao articleDao = session.getMapper(IArticleDao.class);
User user = new User();
user.setId(4);
Article queryVO = new Article();
queryVO.setUser(user);
List<Article> articles = articleDao.findArticles(queryVO);
for(Article article : articles){
System.out.println(article.getTitle()+":"+article.getUser().getUserName());
}
注:实验结果articles应该返回多条数据时只返回一条,删除association配置就返回正常,换成mybatis-3.1.1.jar后问题解决。后来又换回mybatis-3.2.6,经测试article的id在sql中的别名不能是id,改为aid后问题解决。
6.SQL打印
添加commons-logging-1.1.1.jar log4j-1.2.17.jar src下新建log4j.properties
log4j.logger.com.hanjun.dao=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.Threshold=DEBUG
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%-5p] [%d] %t %c - %m%n
三、与spring整合
1.加入jar包
commons-dbcp.jar
commons-logging-1.1.1.jar
commons-pool.jar
mybatis-spring-1.2.2.jar
spring-aop-3.2.8.RELEASE.jar
spring-beans-3.2.8.RELEASE.jar
spring-context-3.2.8.RELEASE.jar
spring-core-3.2.8.RELEASE.jar
spring-expression-3.2.8.RELEASE.jar
spring-jdbc-3.2.8.RELEASE.jar
spring-tx-3.2.8.RELEASE.jar
2.src下新建applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="Index of /schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="Index of /schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
<property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:XE" />
<property name="username" value="zhanghanjun" />
<property name="password" value="pwd123" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--dataSource属性指定要用到的连接池-->
<property name="dataSource" ref="dataSource"/>
<!--configLocation属性指定mybatis的核心配置文件-->
<property name="configLocation" value="Configuration.xml"/>
</bean>
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!--sqlSessionFactory属性指定要用到的SqlSessionFactory实例-->
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<!--mapperInterface属性指定映射器接口,用于实现此接口并生成映射器对象-->
<property name="mapperInterface" value="com.hanjun.dao.IUserDao" />
</bean>
</beans>
优化:sqlSessionFactory增加<property name="mapperLocations" value="classpath*:com/hanjun/entity/*.xml" />,Configuration.xml中的mappers配置可删除
3.测试
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao= ctx.getBean("userDao",IUserDao.class);
User user = userDao.selectUserByID(4);
System.out.println(user.getUserAddress());
4.优化。删除bean:userDao,增加bean
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.hanjun.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
说明:让mybatis自动生指定包下dao接口的实现类。红色行指定使用的sqlSessionFactory,可省略,但配置中有多个sqlSessionFactory时,不能省略。mybatis自动生成的dao的bean的id和dao接口名称相同,在使用ctx.getBean时注意一下,也可直接通过接口dao.class获取bean。
文件分类优化:将实体类的配置文件移到dao接口的包下,并将文件名称改为和接口dao的名称一致,sqlSessionFactory的mapperLocations修改为com/hanjun/dao/*.xml。
四、与spring mvc整合
1.添加jar包
spring-web-3.2.8.RELEASE.jar
spring-webmvc-3.2.8.RELEASE.jar
2.web.xml添加
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextCleanupListener</listener-class>
</listener>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
说明:ContextCleanupListener用于展示层显示完成后清除Controller,因为Controller要在jsp中防问,其生命周期不能在Controller方法结束后就销毁。
3.WEB-INF下新建mvc-dispatcher-servlet.xml
<beans xmlns="Index of /schema/beans"
xmlns:context="Index of /schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
Index of /schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
Index of /schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.hanjun.controller" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
4.新建com.hanjun.controller.UserController
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
IUserDao userDao;
@RequestMapping("/single")
public ModelAndView listall(HttpServletRequest request,HttpServletResponse response){
User user=userDao.selectUserByID(4);
ModelAndView mav=new ModelAndView("index");
mav.addObject("user",user);
return mav;
}
}
5.修改WebRoot下index.jsp
<%@page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<body>
userName:${user.userName }
</body>
</html>
6.测试:
启动tomcat,打开浏览器,输入url: http://localhost:8090/mybatis/user/single 注:url根据自己的服务端口和项目web名称输入。
五、动态SQL
1.if 语句
<select id="dynamicIfTest" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<if test="title != null">
and title = #{title}
</if>
</select>
2.choose (when,otherwize) ,相当于java 语言中的 switch
<select id="dynamicChooseTest" parameterType="Blog" resultType="Blog">
select * from t_blog where 1 = 1
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="content != null">
and content = #{content}
</when>
<otherwise>
and owner = "owner1"
</otherwise>
</choose>
</select>
说明:当when中有条件满足的时候,就会跳出choose,即所有的when和otherwise条件中,只有一个会输出,当所有的我很条件都不满足的时候就输出otherwise中的内容
3.where
<select id="dynamicWhereTest" parameterType="Blog" resultType="Blog">
select * from t_blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="content != null">
and content = #{content}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</where>
</select>
说明:where的作用是,当有条件输出时where才输出,where后面是and或or时也会智能移除。
4.set (主要用于更新时)
<update id="dynamicSetTest" parameterType="Blog">
update t_blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="content != null">
content = #{content},
</if>
<if test="owner != null">
owner = #{owner}
</if>
</set>
where id = #{id}
</update>
5.foreach
<select id="dynamicForeachTest" resultType="Blog">
select * from t_blog where id in
<foreach collection="list" index="index" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</select>
说明:collection指定集合变量名,当只有一个参数时可直接写为list;index为循环中的索引;item指定循环中单个元素的引用名;open指定循环前输出的内容;separator指定多次循环之间输出的内容;close指定循环结束后输出的内容。
六、拦截器
1.mybatis的拦截器实现
public class PluginTest {
public static void main(String[] args) {
Map map = new HashMap();
map = (Map) new MyMapPlugin().plugin(map);
System.out.println(map.get("aa")); //结果为Always
}
@Intercepts( { @Signature(type = Map.class, method = "get", args = { Object.class }) })
public static class MyMapPlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return "Always";
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
}
2.拦截打印SQL,新建com.hanjun.intercep.MybatisInterceptor.java
@Intercepts( {
@Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class }),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class }) })
public class MybatisInterceptor implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
String sqlId = mappedStatement.getId();
BoundSql boundSql = mappedStatement.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
long start = System.currentTimeMillis();
Object returnValue = invocation.proceed();
long end = System.currentTimeMillis();
long time = (end - start);
if (time > 1) {
String sql = getSql(configuration, boundSql, sqlId, time);
System.err.println(sql);
}
return returnValue;
}
public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
String sql = showSql(configuration, boundSql);
StringBuilder str = new StringBuilder(100);
str.append(sqlId);
str.append(":");
str.append(sql);
str.append(":");
str.append(time);
str.append("ms");
return str.toString();
}
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat.getDateTimeInstance(
DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
public static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (parameterMappings.size() > 0 && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",getParameterValue(parameterObject));
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties0) {
}
}
applicationContext.xml中sqlSessionFactory添加
<property name="plugins">
<list>
<bean class="com.hanjun.intercept.MybatisInterceptor"></bean>
</list>
</property>
注:plugins中配置的拦截器只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。
七、拦截器实现分页
前言:
Mybatis拦截器常常会被用来进行分页处理。我们知道要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前也会产生一个包含Sql语句的Statement对象,而且对应的Sql语句是在Statement之前产生的,所以我们就可以在它成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用StatementHandler对象的prepare方法,即调用invocation.proceed()。更改Sql语句这个看起来很简单,而事实上来说的话就没那么直观,因为包括sql等其他属性在内的多个属性都没有对应的方法可以直接取到,它们对外部都是封闭的,是对象的私有属性,所以这里就需要引入反射机制来获取或者更改对象的私有属性的值了。对于分页而言,在拦截器里面我们常常还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。
1.新建工具类com.hanjun.tool.ReflectUtil,下面用得着。
public class ReflectUtil {
/**
* 利用反射获取指定对象的指定属性
*
* @param obj 目标对象
* @param fieldName 目标属性
* @return 目标属性的值
*/
public static Object getFieldValue(Object obj, String fieldName) {
Object result = null;
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
field.setAccessible(true);
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
return result;
}
/**
* 利用反射获取指定对象里面的指定属性
*
* @param obj 目标对象
* @param fieldName 目标属性
* @return 目标字段
*/
private static Field getField(Object obj, String fieldName) {
Field field = null;
for (Class<?> clazz = obj.getClass(); clazz != Object.class; clazz = clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
// 这里不用做处理,子类没有该字段可能对应的父类有,都没有就返回null。
}
}
return field;
}
/**
* 利用反射设置指定对象的指定属性为指定的值
*
* @param obj 目标对象
* @param fieldName 目标属性
* @param fieldValue 目标值
*/
public static void setFieldValue(Object obj, String fieldName,Object fieldValue) {
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
try {
field.setAccessible(true);
field.set(obj, fieldValue);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
2.新建com.hanjun.entity.Page
/**
* 对分页的基本数据进行一个简单的封装
*/
public class Page extends HashMap<String,Object> {
private static final long serialVersionUID = -6413947133626087489L;
private int pageNo = 1;//页码,默认是第一页
private int pageSize = 15;//每页显示的记录数,默认是15
private int totalRecord;//总记录数
private int totalPage;//总页数
private List<?> results;//对应的当前页记录
private Object obj;
public Page(Object obj) {
this.obj=obj;
}
public Object get(Object key) {
return ReflectUtil.getFieldValue(obj, key.toString());
}
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(int totalRecord) {
this.totalRecord = totalRecord;
//在设置总页数的时候计算出对应的总页数,在下面的三目运算中加法拥有更高的优先级,所以最后可以不加括号。
int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
this.setTotalPage(totalPage);
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
@SuppressWarnings("unchecked")
public <T> List<T> getResults() {
return (List<T>) results;
}
public void setResults(List<?> results) {
this.results = results;
}
}
3.新建com.hanjun.intercept.PageInterceptor.java
@Intercepts( { @Signature(method = "prepare", type = StatementHandler.class, args = { Connection.class }) })
public class PageInterceptor implements Interceptor {
private String databaseType;// 数据库类型,不同的数据库有不同的分页方法
/**
* 拦截后要执行的方法
*/
public Object intercept(Invocation invocation) throws Throwable {
// 对于StatementHandler其实只有两个实现类,一个是RoutingStatementHandler,另一个是抽象类BaseStatementHandler,
// BaseStatementHandler有三个子类,分别是SimpleStatementHandler,PreparedStatementHandler和CallableStatementHandler,
// SimpleStatementHandler是用于处理Statement的,PreparedStatementHandler是处理PreparedStatement的,而CallableStatementHandler是
// 处理CallableStatement的。Mybatis在进行Sql语句处理的时候都是建立的RoutingStatementHandler,而在RoutingStatementHandler里面拥有一个
// StatementHandler类型的delegate属性,RoutingStatementHandler会依据Statement的不同建立对应的BaseStatementHandler,即SimpleStatementHandler、
// PreparedStatementHandler或CallableStatementHandler,在RoutingStatementHandler里面所有StatementHandler接口方法的实现都是调用的delegate对应的方法。
// 我们在PageInterceptor类上已经用@Signature标记了该Interceptor只拦截StatementHandler接口的prepare方法,又因为Mybatis只有在建立RoutingStatementHandler的时候
// 是通过Interceptor的plugin方法进行包裹的,所以我们这里拦截到的目标对象肯定是RoutingStatementHandler对象。
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
// 通过反射获取到当前RoutingStatementHandler对象的delegate属性
StatementHandler delegate = (StatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
// 获取到当前StatementHandler的
// boundSql,这里不管是调用handler.getBoundSql()还是直接调用delegate.getBoundSql()结果是一样的,因为之前已经说过了
// RoutingStatementHandler实现的所有StatementHandler接口方法里面都是调用的delegate对应的方法。
BoundSql boundSql = delegate.getBoundSql();
// 拿到当前绑定Sql的参数对象,就是我们在调用对应的Mapper映射语句时所传入的参数对象
Object obj = boundSql.getParameterObject();
// 这里我们简单的通过传入的是Page对象就认定它是需要进行分页操作的。
if (obj instanceof Page) {
Page page = (Page) obj;
// 通过反射获取delegate父类BaseStatementHandler的mappedStatement属性
MappedStatement mappedStatement = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
// 拦截到的prepare方法参数是一个Connection对象
Connection connection = (Connection) invocation.getArgs()[0];
// 获取当前要执行的Sql语句,也就是我们直接在Mapper映射语句中写的Sql语句
String sql = boundSql.getSql();
// 给当前的page参数对象设置总记录数
this.setTotalRecord(page, mappedStatement, connection);
// 获取分页Sql语句
String pageSql = this.getPageSql(page, sql);
// 利用反射设置当前BoundSql对应的sql属性为我们建立好的分页Sql语句
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
return invocation.proceed();
}
/**
* 拦截器对应的封装原始对象的方法
*/
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 设置注册拦截器时设定的属性(拦截器配置在Configuration.xml可使用)
*/
public void setProperties(Properties properties) {
this.databaseType = properties.getProperty("databaseType");
}
/**
* 根据page对象获取对应的分页查询Sql语句,这里只做了两种数据库类型,Mysql和Oracle 其它的数据库都 没有进行分页
*
* @param page 分页对象
* @param sql 原sql语句
* @return
*/
private String getPageSql(Page page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(databaseType)) {
return getMysqlPageSql(page, sqlBuffer);
} else if ("oracle".equalsIgnoreCase(databaseType)) {
return getOraclePageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
/**
* 获取Mysql数据库的分页查询语句
*
* @param page 分页对象
* @param sqlBuffer 包含原sql语句的StringBuffer对象
* @return Mysql数据库分页语句
*/
private String getMysqlPageSql(Page page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Mysql中记录的位置是从0开始的。
int offset = (page.getPageNo() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
/**
* 获取Oracle数据库的分页查询语句
*
* @param page 分页对象
* @param sqlBuffer 包含原sql语句的StringBuffer对象
* @return Oracle数据库的分页查询语句
*/
private String getOraclePageSql(Page page, StringBuffer sqlBuffer) {
// 计算第一条记录的位置,Oracle分页是通过rownum进行的,而rownum是从1开始的
int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
sqlBuffer.insert(0, "select tt.*, rownum rn from (").append(") tt where rownum <= ").append(offset + page.getPageSize()-1);
sqlBuffer.insert(0, "select * from (").append(") where rn >= ").append(offset);
// 上面的Sql语句拼接之后大概是这个样子:
// select * from (select tt.*, rownum rn from (select * from t_user) tt
// where rownum <= 30) where rn >= 16
return sqlBuffer.toString();
}
/**
* 给当前的参数对象page设置总记录数
*
* @param page Mapper映射语句对应的参数对象
* @param mappedStatement Mapper映射语句
* @param connection 当前的数据库连接
*/
private void setTotalRecord(Page page, MappedStatement mappedStatement, Connection connection) {
// 获取对应的BoundSql,这个BoundSql其实跟我们利用StatementHandler获取到的BoundSql是同一个对象。
// delegate里面的boundSql也是通过mappedStatement.getBoundSql(paramObj)方法获取到的。
BoundSql boundSql = mappedStatement.getBoundSql(page);
// 获取到我们自己写在Mapper映射语句中对应的Sql语句
String sql = boundSql.getSql();
// 通过查询Sql语句获取到对应的计算总记录数的sql语句
String countSql = this.getCountSql(sql);
// 通过BoundSql获取对应的参数映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
// 利用Configuration、查询记录数的Sql语句countSql、参数映射关系parameterMappings和参数对象page建立查询记录数对应的BoundSql对象。
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
// 通过mappedStatement、参数对象page和BoundSql对象countBoundSql建立一个用于设定参数的ParameterHandler对象
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
// 通过connection建立一个countSql对应的PreparedStatement对象。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
// 通过parameterHandler给PreparedStatement对象设置参数
parameterHandler.setParameters(pstmt);
// 之后就是执行获取总记录数的Sql语句和获取结果了。
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
// 给当前的参数page对象设置总记录数
page.setTotalRecord(totalRecord);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 根据原Sql语句获取对应的查询总记录数的Sql语句
*
* @param sql
* @return
*/
private String getCountSql(String sql) {
int index = sql.indexOf("from");
return "select count(1) " + sql.substring(index);
}
public String getDatabaseType() {
return databaseType;
}
public void setDatabaseType(String databaseType) {
this.databaseType = databaseType;
}
}