版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
完整代码见github:GitHub地址-银行转账系统
Mybatis官方文档:http://www.mybatis.org/mybatis-3/zh/dynamic-sql.html
一个MyBatis练习的Demo,用于学习MyBatis接口绑定方案及多参数传递、动态SQL、ThreadLocal线程容器配合Filter使用获取SqlSession
运行效果(search.jsp页面):
项目结构:
准备:数据库数据
create table account(
id int(10) primary key auto_increment,
accno varchar(18) unique,
pwd varchar(20),
balance double,
name varchar(20)
);
insert into account values(default,'10001','zhangsan',1000,'张三');
insert into account values(default,'10002','lisi',2000,'李四');
insert into account values(default,'10003','wangwu',3000,'王五');
insert into account values(default,'10004','laoda',4000,'老大');
create table log(
id int(10) primary key auto_increment,
accout varchar(18),
accin varchar(18),
money double,
reason varchar(50)
);
一.MyBatis 接口绑定方案及多参数传递
- 作用:实现创建一个接口后,把 mapper.xml 由 mybatis 生成接口的实现类,通过调用接口对象,就可以获取 XXXmapper.xml 中编写的sql.
- 后面mybatis 和spring 整合时使用的是这个方案.
- 实现步骤:
3.1 创建一个接口
3.1.1 接口包名和接口名与 mapper.xml 中<mapper>
namespace相同
3.1.2 接口中方法名和 mapper.xml 标签的id 属性相同
3.2 在 mybatis.xml 中使用<package>
进行扫描接口和mapper.xml - 代码实现步骤:
4.1 在mybatis.xml 中<mappers>
下使用<package>
,name=""
中写XXXmapper.xml所在的包名+类名
mybatis.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>
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<!-- 给类起别名 -->
<typeAliases>
<package name="cn.hanquan.pojo"/>
</typeAliases>
<environments default="default">
<environment id="default">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="g67108864"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- <mapper resource="cn/hanquan/mapper/AccountMapper.xml" /> -->
<!-- <mapper resource="cn/hanquan/mapper/LogMapper.xml" /> -->
<package name="cn.hanquan.mapper"></package>
</mappers>
</configuration>
4.2 在 cn.hanquan.mapper 下新建接口
- 接口中的方法名称与对应xml文件中的
id
相同 - 接口中的返回值类型与xml文件中的
resultType
相同
package cn.hanquan.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Param;
import cn.hanquan.pojo.Log;
public interface LogMapper {
/**
* 插入一条转账记录
* @param log 被插入的记录
* @return 成功影响的数据库行数
*/
int insLog(String accOut, String accIn, double money, String reason);
/**
* 分页查询数据
* @param map 表示参数的键值对
* @return
*/
List<Log> selByPage(int pageStart, int pageSize);
/**
* 查询总记录条数
* @return 总记录条数
*/
int selCount();
/**
* 动态sql语句
* @param accIn 收款账号
* @param accOut 付款账号
* @return
*/
List<Log> selDynamic(@Param("accIn") String accIn, @Param("accOut") String accOut);
/**
* 修改转账记录
* @param log
*/
void updReason(Log log);
}
4.3 在cn.hanquan.mapper 新建一个LogMapper.xml
namespace=""
必须和接口全限定路径(包名+类名)一致id=""
值必须和接口中方法名相同- 如果接口中方法为多个参数,省略
parameterType=""
。因为parameterType=""
只能写一个参数。或者使用map。
<?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">
<!-- namespace必须和接口全限定路径一致 -->
<mapper namespace="cn.hanquan.mapper.LogMapper">
<!-- 插入log信息 -->
<insert id="insLog">
insert into log values(default,#{0},#{1},#{2},#{3})
</insert>
<!-- limit分页查询 -->
<select id="selByPage" resultType="Log">
select * from log limit #{0}, #{1}
</select>
<!-- 查询总数 -->
<select id="selCount" resultType="int">
select count(*) from log
</select>
<!-- 动态sql查询 -->
<select id="selDynamic" resultType="Log">
select * from log
<where>
<!-- OGNL 表达式,直接写key对象的属性,用and链接 -->
<if test="accIn!=null and accIn!=''">
and accIn=#{accIn}
</if>
<if test="accOut!=null and accOut!=''">
and accOut=#{accOut}
</if>
</where>
</select>
<!-- 修改转账原因 -->
<!-- id=#{id},的目的是防止<set>中没有内容,mybatis不生成set关键字导致的sql语法错误 -->
<update id="updReason" parameterType="Log">
update log
<set>
id=#{id},
<if test="accIn!=null and accIn!=''">
accin=#{accIn},
</if>
<if test="accOut!=null and accOut!=''">
accout=#{accOut},
</if>
<if test="money!=null and money!=''">
money=#{money},
</if>
<if test="reason!=null and reason!=''">
reason=#{reason},
</if>
</set>
where id=#{id}
</update>
</mapper>
- 多参数实现办法
5.1 在接口中声明方法
5.2 在mapper.xml 中添加
5.2.1#{}
中使用0,1,2
或param1,param2
- 可以使用注解方式
6.1 在接口中声明方法
实际上,mybatis 把参数转换为 map 了,其中@Param("key")
参数内容就是 map 的 value
用这种方式,注解外面的变量名称String accin
可以被写成任意名称,比如String accin666
6.2 在mapper.xml 中添加
6.2.1#{}
里面写@Param("内容")
参数中内容
二.动态SQL
- 根据不同的条件需要执行不同的SQL 命令,称为动态SQL
- MyBatis 中动态 SQL 在 mapper.xml 中添加逻辑判断等.
- If 使用(来源:sxt)
<select id="selByAccinAccout" resultType="log">
select * from log where 1=1
<!-- OGNL 表达式,直接写key 或对象的属性.不需要添加任何特字符号-->
<if test="accin!=null and accin!=''">
and accin=#{accin}
</if>
<if test="accout!=null and accout!=''">
and accout=#{accout}
</if>
</select>
<where>
4.1 当编写 where 标签时,如果内容中第一个是and,去掉第一个and
4.2 如果<where>
中有内容,会生成 where 关键字,如果没有内容,不生成 where 关键字
4.3 使用示例(来源:sxt)
4.3.1 使用<where>
,比直接使用<if>
少写一句where 1=1
<select id="selByAccinAccout" resultType="log">
select * from log
<where>
<if test="accin!=null and accin!=''">
and accin=#{accin}
</if>
<if test="accout!=null and accout!=''">
and accout=#{accout}
</if>
</where>
</select>
<choose>
,<when>
,<otherwise>
5.1 只要有一个成立,其他都不执行
5.2 代码示例(来源:sxt)
5.2.1 如果 accin 和 accout 都不是null,不是""
,生成的 sql 中只有where accin=?
<select id="selByAccinAccout" resultType="log">
select * from log
<where>
<choose>
<when test="accin!=null and accin!=''">
and accin=#{accin}
</when>
<when test="accout!=null and accout!=''">
and accout=#{accout}
</when>
</choose>
</where>
</select>
<set>
用在修改SQL 中set 从句
6.1 作用:去掉最后一个逗号
6.2 作用:如果<set>
里面有内容,就生成 set 关键字,如果没有,就不生成 set 关键字
6.3 示例(来源:本项目源码)
6.3.1id=#{id}
目的:防止<set>
中没有内容,mybatis 不生成 set 关键字导致的SQL语法错误.
<!-- 修改转账原因 -->
<!-- id=#{id},的目的是防止<set>中没有内容,mybatis不生成set关键字导致的sql语法错误 -->
<update id="updReason" parameterType="Log">
update log
<set>
id=#{id},
<if test="accIn!=null and accIn!=''">
accin=#{accIn},
</if>
<if test="accOut!=null and accOut!=''">
accout=#{accOut},
</if>
<if test="money!=null and money!=''">
money=#{money},
</if>
<if test="reason!=null and reason!=''">
reason=#{reason},
</if>
</set>
where id=#{id}
</update>
- Trim
7.1prefix
在前面添加内容
7.2prefixOverrides
去掉前面内容
7.3suffix
在后面添加内容
7.4suffixOverrieds
去掉后面内容
7.5 执行顺序:先去掉内容,后添加内容
7.6 代码示例(来源:sxt)
<update id="upd" parameterType="log">
update log
<trim prefix="set" suffixOverrides=",">
a=a,
</trim>
where id=100
</update>
<bind>
8.1 作用:给参数重新赋值
8.2 场景:模糊查询、在原内容前或后添加内容(如$
符号)
8.3 示例(来源:sxt)
<select id="selByLog" parameterType="log" resultType="log">
<bind name="accin" value="'%'+accin+'%'"/>
#{money}
</select>
<foreach>
标签
9.1 循环参数内容,在SQL语句内容的前后添加内容、添加分隔符功能
9.2 适用场景:in 查询、批量新增( 但是 mybatis 中 foreach 效率比较低)
9.2.1 如果希望批量新增,SQL 命令是insert into log VALUES (default,1,2,3),(default,2,3,4),(default,3,4,5)
9.2.2session.openSession()
必须指定: 底层JDBC 的PreparedStatement.addBatch();
factory.openSession(ExecutorType.BATCH);
9.3 示例
9.3.1 collection=””
要遍历的集合
9.3.2 item
迭代变量, #{迭代变量名}
获取内容
9.3.3 open
循环后左侧添加的内容
9.3.4 close
循环后右侧添加的内容
9.3.5 separator
每次循环时,元素之间的分隔符
<select id="selIn" parameterType="list" resultType="log">
select * from log where id in
<foreach collection="list" item="abc" open="(" close=")" separator=",">
#{abc}
</foreach>
</select>
<sql>
和<include>
10.1 某些SQL 片段如果希望复用,可以使用定义这个片段
<sql id="mysql">
id,accin,accout,money
</sql>
10.2 在<select>
或<delete>
或<update>
或<insert>
中使用<include>
引用
<select id="">
select <include refid="mysql"></include> from log
</select>
三、ThreadLocal 线程容器
- 线程容器,给线程绑定一个Object 内容,后只要线程不变,可以随时取出。
- 如果改变线程,无法取出内容.
- ThreadLocal的使用:语法示例
final ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("测试");
new Thread(){
public void run() {
String result = threadLocal.get();
System.out.println("结果:"+result);
};
}.start();
- 使用ThreadLocal优化service层代码:不必反复写以下22-24行
通过写一个MyBatisUtil.java
(在里面保存ThreadLocal),同时新建一个FilterOpenSessionInView.java
- 在里面每次收到请求之后都调用一下
MyBatisUtil.getSession()
获取session对象(类似单例模式) - 处理完请求之后再调用一下
MyBatisUtil.closeSession()
关闭session - 这样可以保证执行的
session.commit()
与启动过滤器时得到的sessionMyBatisUtil.getSession()
是同一个session
实现如下:
- OpenSessionInView.java(Filter过滤器)
package cn.hanquan.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.apache.ibatis.session.SqlSession;
import cn.hanquan.util.MyBatisUtil;
@WebFilter("/*")
public class OpenSessionInView implements Filter {
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain filterChain)
throws IOException, ServletException {
SqlSession session = MyBatisUtil.getSession();
System.out.println("过滤器:获取session");
try {
filterChain.doFilter(req, resp);
session.commit();
} catch (Exception e) {
session.rollback();
e.printStackTrace();
}finally {
MyBatisUtil.closeSession();
System.out.println("过滤器:关闭session");
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
}
- MyBatisUtil.java(用于创建ThreadLocal,并从中获取唯一的session对象,单例)
package cn.hanquan.util;
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;
public class MyBatisUtil {
/**
* factory实例化耗费性能,因此保证只有一个factory
*/
private static SqlSessionFactory factory;
/**
* 线程容器,给线程绑定一个Object内容,只要线程不变,可以随时取出
*/
private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();
/**
* 初始化一个factory
*/
static {
try {
InputStream is = Resources.getResourceAsStream("mybatis.xml");
factory = new SqlSessionFactoryBuilder().build(is);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取一个session对象
* @return session对象
*/
public static SqlSession getSession() {
SqlSession session = threadLocal.get();
if (session == null) {
threadLocal.set(factory.openSession());
}
return threadLocal.get();
}
/**
* 关闭session,并且删除ThreadLocal线程容器中保留的session内容
*/
public static void closeSession() {
SqlSession session = threadLocal.get();
if (session != null) {
session.close();
}
threadLocal.set(null);
}
}
- LogServiceImpl.java(Service层代码的优化,不用每次都重新创建
SqlSessionFactory
)
package cn.hanquan.service.impl;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
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 cn.hanquan.mapper.LogMapper;
import cn.hanquan.pojo.Log;
import cn.hanquan.pojo.PageInfo;
import cn.hanquan.service.LogService;
import cn.hanquan.util.MyBatisUtil;
public class LogServiceImpl implements LogService {
@Override
public PageInfo showPage(int pageSize, int pageNum) throws IOException {
// InputStream is = Resources.getResourceAsStream("mybatis.xml");
// SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// SqlSession session = factory.openSession();
SqlSession session = MyBatisUtil.getSession();
LogMapper logMapper = session.getMapper(LogMapper.class);
PageInfo pageInfo = new PageInfo();
pageInfo.setPageNum(pageNum);
pageInfo.setPageSize(pageSize);
// 查询结果
int pageStart = pageSize * (pageNum - 1);
List<Log> list = logMapper.selByPage(pageStart, pageSize);
pageInfo.setDataList(list);
System.out.println("LogService中的list:" + list);
// 总页数
int count = logMapper.selCount();
pageInfo.setTotal((count % pageSize == 0 ? count / pageSize : count / pageSize + 1));
System.out.println("LogService中的count:" + count);
// session.close();
return pageInfo;
}
@Override
public List<Log> search(String accIn, String accOut) throws IOException {
// InputStream is = Resources.getResourceAsStream("mybatis.xml");
// SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// SqlSession session = factory.openSession();
SqlSession session = MyBatisUtil.getSession();
LogMapper logMapper = session.getMapper(LogMapper.class);
List<Log> list = logMapper.selDynamic(accIn, accOut);
System.out.println("LogService中的list:"+list);
// session.close();
return list;
}
@Override
public void modifyLog(Log log) throws IOException {
// InputStream is = Resources.getResourceAsStream("mybatis.xml");
// SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
// SqlSession session = factory.openSession();
SqlSession session = MyBatisUtil.getSession();
//调用修改
LogMapper logMapper = session.getMapper(LogMapper.class);
logMapper.updReason(log);
System.out.println("LogService中的log:"+log);
// session.commit();
// session.close();
}
}
四、缓存
- 应用程序和数据库交互的过程是一个相对比较耗时的过程
- 缓存存在的意义:让应用程序减少对数据库的访问,提升程序运行效率
- MyBatis 中,默认 SqlSession 缓存开启
3.1 同一个 SqlSession 对象调用同一个<select>
时,只有第一次调用会访问数据库,第一次之后,把查询结果缓存到 SqlSession 缓存区(内存)中,以后再调用时,直接从内存中拿数据。
3.2 缓存的是statement 对象 (简单记忆:必须是同一个<select>
)
3.2.1 在myabtis 中,一个<select>
对应一个 statement 对象
3.3 有效范围必须是同一个 SqlSession 对象 - 缓存流程
4.1 步骤一: 先去缓存区中找是否存在statement
4.2 步骤二:返回结果
4.3 步骤三:如果没有缓存statement 对象,去数据库获取数据
4.4 步骤四:数据库返回查询结果
4.5 步骤五:把查询结果放到对应的缓存区中
SqlSessionFactory
缓存
5.1 又叫:二级缓存
5.2 有效范围:同一个factory 内哪个SqlSession 都可以获取
5.3 什么时候使用二级缓存:
5.3.1 当数据频繁被使用,很少被修改
5.4 使用二级缓存步骤
5.4.1 在 mapper.xml 中添加
5.4.2 如果不写readOnly=”true”
,需要把实体类序列化implements serializable
<cache readOnly="true"></cache>
5.5 当SqlSession 对象close()
时,或commit()
时,会把 SqlSession 缓存的数据刷(flush)到 SqlSessionFactory 缓存区中