MyBatis
概述
框架:封装通用功能,软件开发中的半成品,简化开发过程。
轻量级的,持久层框架,负责完成java和数据库的通信。
代码分布:DAO+Service
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
ORM
概念:Object Relational Mapping,对象关系映射。
目标:在【java对象】 和 【关系表】 建立映射
:简化两者之间的通信过程。可以直接通信。
1. 搭建流程
1 导入依赖
// pom.xml
<!-- mybatis 依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- mysql驱动 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
Mapper映射文件写在resources目录下不需要下面配置
//pom.xml,使得src/main/java下的xml文件可以进入编译范围
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
定义O和R
create table t_user(
id int primary key auto_increment,
name varchar(20),
gender char(1),-- tinyint(1)
create_time datetime
)default charset=utf8 engine=innodb;
class User{
private Integer id;
private String name;
private Boolean gender;
private Date createTime;
//get/set....
}
M-定义映射文件
DAO接口定义不变,映射文件即Mapper文件替换之前的DAO实现
public interface UserDAO {
public List<User> queryAll();
public User queryOne(Integer id);
public List<User> queryManyByName(String name);
public List<User> queryManyByDate(Date craeteTime);
// 明确为参数定义别名,用于mapper文件中获取
public List<User> queryUserByIdAndName(@Param("id") Integer id, @Param("name") String name);
}
// UserDAO.xml 重点搞清楚 何为映射
<!-- 不用定义dao的实现类,此映射文件等价于dao的实现 -->
<!-- namespace="dao接口路径"-->
<?xml version="1.0" encoding="UTF-8"?>
<!-- dtd:document type definition 配置文件规范 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhj.dao.UserDAO">
<!--
根据id查询用户,User queryOne(Integer id)
select:配置查询语句
id:可以通过id找到执行的statement,对应dao中的一个方法名
parameterType: 参数类型 【int long float double string boolean date 自建类型(com.zhj.domain.User)】
resultType:结果类型,查询的结果会被封装到对应类型的对象中
#{}:相当于占位符
#{id}:就是把 “queryOne” 方法的名为id的参数填充到占位上
-->
<select id="queryOne" parameterType="int" resultType="com.zhj.domain.User">
select id as id,name as name,gender as gender,create_time as createTime
from t_user
where id=#{id}
</select>
<!-- 注意:返回类型实际是List<User> , 但此处只需定义User即可 -->
<select id="queryAll" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from t_user
</select>
<select id="queryManyByName" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from t_user
where name like #{name}
</select>
<select id="queryManyByDate" parameterType="date" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from t_user
where create_time=#{createTime}
</select>
<!-- 注意:此时方法有多个参数,【#{xx}】取值时,需要@param支持 -->
<select id="queryUsers" resultType="com.qianfeng.pojo.User"
parameterType="com.qianfeng.pojo.User">
select id,name,gender,birth from t_user where id=#{id} or name like #{name}
</select>
</mapper>
搭建配置文件
<?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>
<!-- 使用 id为“development”的环境信息 -->
<environments default="development">
<environment id="development">
<!-- 配置JDBC事务控制,由mybatis进行管理 -->
<transactionManager type="JDBC"></transactionManager>
<!-- 配置数据源,采用mybatis连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<!-- 【&】是特殊字符,【&】是【&】的转义 -->
<property name="url" value="jdbc:mysql://localhost:3306/db9?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="111111" />
</dataSource>
</environment>
</environments>
<!-- 加载映射文件 -->
<mappers>
<!-- 使用资源的路径
<mapper resource="com/zhj/dao/UserDAO.xml"/> -->
<!-- 加载某个包下的映射文件 (推荐)
要求:Mapper接口的名称与映射文件名称一致 且 必须在同一个目录下 -->
<package name="com.zhj.dao"/>
</mappers>
</configuration>
测试使用
SqlSessionFactoryBuilder
:该对象负责加载MyBatis配置文件 并 构建SqlSessionFactory实例SqlSessionFactory
:每一个MyBatis的应用程序都以一个SqlSessionFactory对象为核心。负责创建SqlSession对象实例。SqlSession
:等价于jdbc中的Connection,用于完成数据操作。
//1、读取配置文件
String resource = "configuration.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//2、根据配置文件创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//3、SqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
数据操作
- 通过SqlSession获得DAO实现(Mapper)
- 通过DAO实现完成具体的数据操作
// 获得UserDAO的实现
// UserDAO userMapper = sqlSession.getMapper(UserDAO.class);
UserDAO userDAO = sqlSession.getMapper(UserDAO.class);
//根据id查询
userDAO.queryOne(1);
//查询所有
userDAO.queryAll();
//根据姓名模糊查询
userDAO.queryManyByName("%zhj%");
//根据日期查询
Date date = new GregorianCalendar(2019, 11, 12).getTime();
userDAO.queryManyByDate(date);
sqlSession.close();//关闭sqlSession
别名
在mapper文件中,`parameterType` 和 `resultType`中使用类型时:
`<select id="xxx" parameterType="com.zhj.domain.Page" resultType="com.zhj.domain.User">`
除mybatis自动映射的类型外,其他类型都要定义完整路径,相对繁琐。可用如下两种方式简化:
// configuration.xml
<configuration>
...
<typeAliases>
<!-- 1. 单独为每个类定义别名,则 "Page"等价于"com.zhj.domain.Page"
<typeAlias type="com.zhj.domain.Page" alias="Page"/>
<typeAlias type="com.zhj.domain.User" alias="User"/>-->
<!-- 2. 定义缺省包,当mapper中的类型没有定义包时,使用此配置作为默认包;
则 “Page” 会自动认为是 “com.zhj.domain”中的“Page”,即“com.zhj.domain.Page”
则 “User” 会自动认为是 “com.zhj.domain”中的“User”,即“com.zhj.domain.User”-->
<package name="com.zhj.domain"/>
</typeAliases>
...
</configuration>
有如上别名配置后,mapper中:
<select id="xxx" parameterType="Page" resultType="User">
参数分离
在mybatis的配置文件中有一项重要且可能需要经常改动的配置,即,数据库连接参数。
可以单独进行管理,方便后续维护。
<!-- 如下四项参数 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/db9?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root" />
<property name="password" value="111111" />
</dataSource>
单独定义参数文件
# 在resources目录下,创建 jdbc.properties文件
# 参数名=参数值
jdbc.user=root
jdbc.password=111111
jdbc.url=jdbc:mysql://localhost:3306/db9?useUnicode=true&characterEncoding=utf8
jdbc.driver=com.mysql.jdbc.Driver
加载参数文件
// 在 configuration.xml中
<configuration>
<!-- 加载参数文件 -->
<properties resource="jdbc.properties"></properties>
....
....
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!-- 使用 ${参数名} 获取参数文件中的值。如此这四项参数需要修改时,需要改动参数文件即可 -->
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
# 和 $ 区分
//如果用
,
则
必
须
用
@
P
a
r
a
m
注
解
,
否
则
,则必须用 @Param注解,否则
,则必须用@Param注解,否则{name}会认为是要从参数中取名为name的属性
public List test3(@Param(“name”) String a);
<!-- 注意${} 就是在做字符拼接,所以此处用了【‘${name}’】而不是【${name}】
类比jdbc的sql语句的拼接:
String name="zhj";
String sql = "select ... from tt2 where name='"+name+"'";//此处是要加单引号的
注意:sql拼接时,有sql注入的风险
-->
<select id="test3" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from tt2
where name = '${name}'
</select>
<!-- 注意#{} 就是在做占位符,所以此处用了【#{name}】而不是【’#{name}‘】
类比jdbc的sql语句的拼接:
String name="zhj";
String sql = "select ... from tt2 where name=?“;//此处是不加单引号的
...
prepareStatement.executeQuery(sql,name);//占位符赋值
-->
<select id="test3" parameterType="string" resultType="com.zhj.domain.User">
select id,name,gender,create_time as createTime
from tt2
where name = #{name}
</select>
动态sql
在映射文件中,定义了要执行的sql语句,mybatis支持在sql语句中填充一些逻辑,是的sql语句可以呈现不同的语义,
即动态sql
IF
在sql中 注入 if
,可以让sql更加灵活,让一个查询语句,可以应对更多查询情景。
重点:== != > < >= <= and or
: 比较字符串需要加引号,比较数字、布尔、null不用加引号
常用:对参数是否为空的判断,动态决定sql语句的组成
情景:对用户可以有通过name 或 gender的搜索,如果没有if
动态逻辑,则是要定义多个<select>
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user
WHERE
<if test="name != null and name!=''">
name=#{name}
</if>
<if test="gender != null">
AND gender=#{gender}
</if>
</select>
<!-- 如上如果gender为null,name不为null,则sql为:
SELECT id,name,gender,regist_time
FROM t_user
WHERE name=#{name}
-->
<!-- 如上如果gender不为null,name不为null,则sql为:
SELECT id,name,gender,regist_time
FROM t_user
WHERE name=#{name} AND gender=#{gender}
-->
Choose
如果在多个判断中,只会有一个是可以成立的,可以使用Choose标签
<!-- 如果id不为空就按id查询。如果id为空但name不为空就按name查询。如果都为空,就查询所有男性用户。 -->
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user
WHERE
<choose> <!-- 从上到下,有任何一个when标签,判断成功则停止判断 -->
<when test="id != null"> <!-- 判断 -->
id > #{id}
</when>
<when test="name !=null"> <!-- 判断 -->
name = #{name}
</when>
<otherwise> <!-- 以上判断都不成立时,执行 -->
gender = '1'
</otherwise>
</choose>
</select>
Where
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time FROM t_user
WHERE
<if test="name != null">
name=#{name}
</if>
<if test="id>=1">
AND id>#{id}
</if>
</select>
<!-- 如果 name=null,id=3,则sql变为:
SELECT id,name,gender,regist_time FROM t_user
WHERE AND id>#{id}
-->
<!-- 如果 name=null,id=0,则sql变为:
SELECT id,name,gender,regist_time FROM t_user
WHERE
-->
<where>
标签中如果没有成立的条件,则不会拼接where语句
;
<where>
标签中如果以 and 或 or开头,会去将其去除。
<select id="queryUsers" parameterType="User" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user
<where>
<if test="name == null">
name=#{name}
</if>
<if test="id>=1">
AND id>#{id}
</if>
</where>
</select>
<!-- 如果 name=null,id=3,则sql变为:
SELECT id,name,gender,regist_time FROM t_user
WHERE id>#{id}
-->
<!-- 如果 name=null,id=0,则sql变为:
SELECT id,name,gender,regist_time FROM t_user
-->
Set
<set>
标签主要用于 update
中
<!-- 只更新非空的字段 -->
<update id="updateUser" parameterType="User">
UPDATE t_user2
SET
<if test="name != null">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="registTime != null">
regist_time = #{registTime}
</if>
WHERE id = #{id}
</update>
<!-- 如果registTime=null,name或gender!=null,则sql为:
UPDATE t_user2
SET name = #{name},gender = #{gender},
WHERE id = #{id}
注意:多了逗号,sql语法错误
-->
使用 <set>
:
<update id="updateUser" parameterType="User">
UPDATE t_user2
<set>
<if test="name != null">
name = #{name},
</if>
<if test="gender != null">
gender = #{gender},
</if>
<if test="registTime != null">
regist_time = #{registTime}
</if>
</set>
WHERE id = #{id}
</update>
<!-- 如果registTime=null,name或gender!=null,则sql为:
UPDATE t_user2
SET name = #{name},gender = #{gender}
WHERE id = #{id}
注意:<set>会自动补充一个 ”set语句“,并将最后一个逗号去除
-->
Foreach
批量查询:id为 1,3,5的数据
//DAO声明为: public List<User> queryUsers2(List<Integer> ids);
<select id="queryUsers2" resultType="User">
SELECT id,name,gender,regist_time
FROM t_user2
WHERE id IN
<foreach collection="list" open="(" separator="," close=")" item="id" index="ind">
#{id}
</foreach>
</select>
<!--
<foreach collection="list" open="(" separator="," close=")" item="id" index="ind">
collection="list" 代表参数是List,如果是数组,要用array
open="(" 以“(” 开头
close=")" 以“)” 结尾
separator="," 值之间用“,”分隔
item="id" 每次遍历的值的临时变量
#{id} 获取每次遍历的值
如上:标签的效果是 (值1,值2,值3)
示例:如果传入 List{1 3 5},则最终的sql:
【SELECT id,name,gender,regist_time
FROM t_user2
WHERE id IN (1,3,5)】
-->
Sql
复用sql语句
<!-- 定义一段sql -->
<sql id="order">
select id,note,price,create_time as createTime
from t_order
</sql>
<!-- 引用sql -->
<select id="queryOrder" parameterType="int" resultType="Order">
<include refid="order"/>
where id = #{id}
</select>
缓存
缓存:将数据库的数据临时的存储起来,以更好的支持查询。
问题:如果有数据,查询频繁且更新极少,此种数据如果依然每次到数据库查询,效率偏低。
解决:将如上数据,临时的存储到内存中,提供对外界的查询服务,进而减少和数据库的通信,提高查询效率。
原理:当查询数据时,查询结果会被缓存在某个内存区域中,核心存储结构={sql:查询结果};
每次发起查询时,会先找到缓存,从中尝试获取数据,如果没有找到数据,再去查数据库,并将在数**
库中查到的结果存入缓存,以供后续查询使用。
一级缓存
存储位置:SqlSession;即一个SqlSession对象发起查询后,查询结果会缓存在自己内部
有效范围:同一个SqlSession的多次查询;即,同一个SqlSession的多次相同sql的查询可以使用一级缓存
开启:不用任何配置,默认启动。
清除:sqlSession.clearCache();
二级缓存
**存储位置:SqlSessionFactory;同一个SqlSessionFactory创建的所有SqlSession发起的查询,查询结果都会缓存在 **
SqlSessionFactory内部。
有效范围:同一个SqlSessionFactory
开启:默认开启,但需要制定哪个DAO的Mapper需要使用二级缓存,定义一个 <cache>
即可
注意:二级缓存必须在sqlSession.commit()
或 sqlSession.close()
之后才生效
清除:sqlSession.rollback();//则查询的结果不会进入二级缓存
应用
<mapper namespace="com.zhj.dao.UserDAO">
<!-- 当前mapper中的所有查询,都进入二级缓存
缓存数据中涉及的pojo一定要实现 Serialiable。
-->
<cache></cache>
<select>...</select>
.....
</mapper>
UserDAO userDAO1 = sqlSession1.getMapper(UserDAO.class);
UserDAO userDAO2 = sqlSession2.getMapper(UserDAO.class);
userDAO1.queryOne(1);
userDAO2.queryOne(1);
// 在开启了二级缓存的情况下,如上代码依然会查询两次数据库。
// userDAO1.queryOne(1);之后缓存只在sqlSession1中,并未进入二级缓存。userDAO2.queryOne(1);无法使用
UserDAO userDAO1 = sqlSession1.getMapper(UserDAO.class);
UserDAO userDAO2 = sqlSession2.getMapper(UserDAO.class);
userDAO1.queryOne(1);
sqlSession1.commit();//close()也可以,因为close内部流程和commit内部流程有对缓存的相同处理
userDAO2.queryOne(1);
// 此时如上代码会只查询一次数据库。
// sqlSession1.commit();执行时,会将查到的数据序列化,存入二级缓存中。userDAO2.queryOne(1)可以使用
PageHelper
使用过程
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>RELEASE</version>
</dependency>
<!--
plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 页号自动回归到合理数值 -->
<property name="reasonable" value="true"/>
</plugin>
</plugins>
<!-- spring等价配置
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor"></bean>
</array>
</property>
</bean>
-->
//使用:
PageHelper.startPage(2,3);// 第2页,每页3条数据,pageNum,pageSize
PageHelper.orderBy("id desc");//可以选择设置排序(可选)
List<User> users = mapper.queryAllUsers();//PageHelper后的第一个查询语句,会被PageHelp增强处理(可观测mysql日志)
for (User user : users) {// users中已经是分页数据
System.out.println(user);
}
//包装一个PageInfo,其中会持有所有分页会用到的信息:当前页号,每页多少条,共多少页,是否为第一页/最后一页,是否有下一页等。
PageInfo<User> pageInfo=new PageInfo<User>(users);
重要提示
PageHelper.startPage
方法重要提示
只有紧跟在PageHelper.startPage
方法后的第一个Mybatis的**查询(Select)**方法会被分页。
请不要配置多个分页插件
请不要在系统中配置多个分页插件(使用Spring时,mybatis-config.xml
和Spring<bean>
配置方式,请选择其中一种,不要同时配置多个分页插件)!
分页插件不支持带有for update
语句的分页
对于带有for update
的sql,会抛出运行时异常,对于这样的sql建议手动分页,毕竟这样的sql需要重视。