目录
概述
MyBatis 是一个 Maven 项目
- 是基于Java的持久层框架
- 简单化 SQL,可定制
- 避免几乎所有的 JDBC
- 可使用简单的 XML、注解、接口为数据库中的记录
安装依赖
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency>
gitHab : https://github.com/mybatis/mybatis-3
中文文档 : https://mybatis.org/mybatis-3/zh/index.html
特点:解除sql与程序代码的耦合(sql写在xml配置文件中)
一.创建MyBatis程序
思路:搭建环境 > 导入MyBatis > 编程 > 测试
1.1 搭建环境
(1)创建数据库
(2)导包和配置
导入依赖
<dependencies>
<!-- 数据库驱动:https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!-- myBatis:https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit:https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>
注:需要配置xml文件打包
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
1.2 创建模块
(1)编写核心配置文件
文件名 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 核心配置文件-->
<configuration>
<environments default="development">
<!--environment 环境-->
<environment id="development">
<!--transactionManager 事务管理-->
<transactionManager type="JDBC"/>
<!--dataSource 数据源-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<!--映射器:每一个 mapper.xml 要在 Mybatis 核心配置文件中注册-->
<!--resource路径必须使用'/',而不能使用'.'-->
<mappers>
<mapper resource="com/zheng/dao/UserMapper.xml"/>
</mappers>
</configuration>
(2)编写工具类
public class MyBatis {
//==== 1.构建 sqlSessionFactory 对象
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//==== 2.从 SqlSessionFactory 中获取 SqlSession
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();//输入参数true则每次事务完成自动提交
}
}
2.3 编程
(1)实体类
(属性私有,get/set)略…
(2)Mapper接口
public interface UserMapper {
List<User> getUserList();
}
(3)Mapper配置文件
映射的sql语句(在一个 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">
<!--namespace属性:绑定一个对应的Mapper接口-->
<mapper namespace="com.zheng.dao.UserMapper">
<!--id:对应方法名 resultType:返回类型(对应方法的泛型)-->
<select id="getUserList" resultType="com.zheng.pojo.User">
select * from mybatis.user
</select>
</mapper>
2.4 测试
public class UserDaoTest {
@Test
public void test(){
//1.获取sqlSession对象
SqlSession sqlSession = MyBatis.getSqlSession();
try{
//2.执行SQL
//获取 UserMapper 接口对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
}
finally {
//3.关闭SqlSession
sqlSession.close();
}
}
}
2.5 补充
(1)作用域问题
SqlSessionFactoryBuilder 工厂创建者
这个类用于获取sqlSessionFactory,之后没有作用。可当做局部变量,需要时才创建。
sqlSessionFactory 数据库连接池
一旦被创建就一直存在,不能丢弃和重建。最佳作用域是应用作用域。
SqlSession
每个线程都有自己的 SqlSession 实例,不能共享。最佳作用域是方法作用域,请求时打开一个sqlSession,返回响应后必须关闭。
不能将其放在静态域或实例变量。
注:【SqlSessionFactoryBuilder】 是工厂创建者,创建完成后便不再需要。【sqlSessionFactory】 是数据库连接池,全局只需要一个连接池即可,所有作用域应该是最高,并且中途不能删除或新建。【SqlSession】 是线程池中取出的一个实例,每次连接完成后需要关闭,才能将分出去的连接还到连接池中,避免占用。
(2)项目结构
二.增删改查
3.1 select
【属性】
-
id:对应 namespace 中的方法名;
-
resultType:sql语句执行的返回值类型;
-
parameterType:参数类型;
【例】
//在接口中添加此方法
//根据id查询用户
User getUserById(int id);
<!--在对应的配置文件中添加对应的查询语句,参数id对应接口方法中的形参-->
<select id="getUserById" resultType="com.zheng.pojo.User" parameterType="int">
select * from mybatis.user where id=#{id}
</select>
3.2 增删改
注:增删改执行结束需要提交事务,sqlSession.commit();
@Test
public void test04(){
SqlSession sqlSession = MyBatis.getSqlSession();
try{
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updataUser(new User(111, "李某迪", "123321"));
System.out.println(i>0?"成功":"失败");
sqlSession.commit();//提交事务
}
finally {
sqlSession.close();
}
}
【例】
//插入数据
int addUser(User u);
//修改数据
int updataUser(User u);
//删除数据
int deletetUser(int id);
<insert id="addUser" parameterType="com.zheng.pojo.User">
insert into user values(#{id},#{name},#{pass})
</insert>
<update id="updataUser" parameterType="com.zheng.pojo.User">
update user set pass=#{pass} where id=#{id}
</update>
<delete id="deletetUser" parameterType="int">
delete from user where id=#{id}
</delete>
3.3 拓展
(1)使用map代替实体类
当实体类对象较大时,为了方便传递多个参数,可用map。一般用于条件查询
接口和配置
//万能map
int addMapUser(Map<String,Object> map);
<!--map传值可直接取出key作为参数-->
<insert id="addMapUser" parameterType="map">
insert into user values(#{uid},#{uname},#{upass})
</insert>
测试类
SqlSession sqlSession = MyBatis.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("uid",123);
map.put("uname","李某迪");
map.put("upass","123456");
mapper.addMapUser(map);
sqlSession.commit();
sqlSession.close();
(2)模糊查询
<!--方法一-->
<select id="getUserLike" resultType="com.zheng.pojo.User">
select * from user where name like #{value}
</select>
<!--方法二:like #{value}"%",但需要放在SQL注入-->
SqlSession sqlSession = MyBatis.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userLike = mapper.getUserLike("李%");
//使用方法二只需要传mapper.getUserLike("李");
for (User user : userLike) {
System.out.println(user);
}
sqlSession.close();
(3)Limit实现分页
核心是SQL实现,map实现传参
List<User> getUserLimit(Map<String,Integer> map);
<!-- 从startIndex开始,查询pageSize个 -->
<select id="getUserLimit" parameterType="map" resultType="user">
select * from user limit #{startIndex},#{pageSize}
</select>
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("startIndex",2);
map.put("pageSize",2);
List<User> userLimit = mapper.getUserLimit(map);
3.4 结果集映射ResultMap
【问题】:解决属性名和字段名不一致的问题
例如:当数据库表字段分别为 id、name、passworld
,而实体类为 id、name、pass
时
查询结果如下:
解决方法:
<!--结果集映射:id=对应的sql字段 type=对应的实体类-->
<resultMap id="UserMap" type="user">
<!--column(列)=数据库中字段 property(属性)=实体类中字段-->
<result column="id" property="id"></result>
<result column="name" property="name"></result>
<result column="passworld" property="pass"></result>
</resultMap>
<select id="getUserList" resultType="user" resultMap="UserMap">
select * from mybatis.user
</select>
四.配置解析
4.0 配置文件
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
配置文档的顶层结构如下
configuration(核心配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- mappers(映射器)
- …(其余了解即可)
4.1 环境配置
(1)多道环境
【说明】MyBatis 可以配置多种环境,即可以有多个environment。例如,开发、测试和生产环境。
但每个 SqlSessionFactory 实例只能选择一种环境。
【使用】通过environments的default属性进行切换,每一个环境对应一个id
(2)事务管理器 transactionManager
type=“[JDBC|MANAGED]” 默认:JDBC
(3)数据源 dataSource:配置 JDBC 连接对象的资源
type=“[UNPOOLED|POOLED|JNDI]” 默认:POOLED
4.2 属性优化
通过 properties 标签实现引用配置文件
【外部文件 db.properties】
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false
uname=root
upass=123
<!--通过该标签导入外部文件。使用格式:${url} -->
<properties resource="db.properties"></properties>
<!--properties标签中可使用多个propertie,与外部文件属性同意,且外部文件优先级高-->
4.3 别名优化
<typeAliases>
<typeAlias alias="Author" type="domain.blog.Author"/>
</typeAliases>
如上配置,使得在任何地方都可以使用别名 Author
代替 domain.blog.Author
也可以使用 package 指定一个包名
<typeAliases>
<package name="domain.blog"/>
</typeAliases>
如上配置,直接使用该包下类名作为该类地址别名,一般不可指定别名名称
思考:如果两个包中类名相同怎么办
@Alias("author") public class Author {...}
使用注解为实体类起别名
常见的 Java 类型内建的类型别名
别名 | 映射 |
---|---|
_int | int |
int | integer |
map | Map |
… | … |
4.4 设置
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存 。 | true\false | true |
lazyLoadingEnabled | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性;否则,每个延迟加载属性会按需加载。懒加载 | true\false | false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | LOG4J\STDOUT_LOGGING | - |
<!--设置例子-->
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
4.5 映射器
【常用写法】
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
即一个 mapper 标签对应一个 mapper 文件
【了解】
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!-- 将包内的映射器接口实现【全部】注册为映射器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
以上两种方法适用于一定的条件,但有以下局限
- 接口和绑定接口的Mapper文件 必须同名
- 接口和绑定接口的Mapper文件 必须在同一目录下
五.日志工厂
用于排错,代替原来的debug
配置文件>设置>logImpl
- LOG4J
- STDOUT_LOGGING(标准日志输出)
具体使用哪一个日志实现,在设置中设定
5.1 STDOUT_LOGGING(标准)
<!--核心配置文件:不用导包直接使用-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
5.2 LOG4J
链接:Log4j入门教程
1.配置和导包
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2.创建一个配置文件 log4j.properties
#将等级分为DEBUG的日志信息输出到console和file这两个目的地
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
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 = [%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./log/myLog.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis = DEBUG
log4j.logger.java.sql = DEBUG
log4j.logger.java.sql.Statement = DEBUG
log4j.logger.java.sql.ResultSet = DEBUG
log4j.logger.java.sql.PreparedStatement = DEBUG
3.log4j的使用
4.自定义日志信息
// Logger所在包:import org.apache.log4j.Logger;
static Logger logger = Logger.getLogger(UserTest.class);
六.注解开发
6.1 使用java注解开发
<!--更改映射-->
<mappers>
<mapper class="zheng.dao.UserMapper"></mapper>
</mappers>
<!--直接在注解中写入SQL-->
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
注1:本质就是使用反射获取类的信息(参数及返回值类型等)
注2:使用注解来映射语句会使代码显得更加简洁,但只能用于简单语句(做不到结果集映射)
6.2 @Param() 注解 【注】
多个基本类型的参数或者String类型参数,需要加上该注解
@Select("select * from user where id>#{to} and id<#{from}")
List<User> getMyUser(@Param("to")int to,@Param("from")int from);
List<User> users = mapper.getMyUser(101,105);//调用
七.Lombok
【说明】Lombox是一款java插件,可以使用注解简化代码,常用于实体类(POJO)简化get/set/构造器等的配置
1)IDEA中安装插件
2)在项目中导入jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
3)参数配置
@Data
- 无参构造器
- get \ set
- equals \ hashCode \ toString
@AllArgsConstructor
@NoArgsConstructor
- 显示有参\无参构造(显示定义无参会消失,需要手动添加)
八.复杂查询
8.1 多对一
//学生实体类:student
public class Student {
private int id;
private String name;
//学生需要关联老师
private Teacher teacher;
}
(1)按查询进行嵌套
public interface StuMapper {
//查询学生信息和对应老师信息
List<Student> getAllUser();
}
<!--子查询:使用 resultMap 作为中间值-->
<mapper namespace="com.zheng.mapper.StuMapper">
<select id="getAllUser" resultMap="ST">
select * from student
</select>
<resultMap id="ST" type="Student">
<!--复杂对象需要单独处理:对象:association;集合:collection-->
<!--column=数据库列 property=实体类属性 javaType=实体类对象 select=子查询-->
<association column="Tid" property="teacher" javaType="Teacher" select="getTea"></association>
</resultMap>
<select id="getTea" resultType="Teacher">
select * from teacher where id=#{id}
</select>
</mapper>
(2)按结果嵌套处理√
<!--联表查询【推荐使用】-->
<select id="getAllUser2" resultMap="ST2">
select s.id,s.name sname,t.name tname
from student s,teacher t
where s.Tid=t.id
</select>
<resultMap id="ST2" type="Student">
<result column="sname" property="name"></result>
<association property="teacher" javaType="Teacher">
<result column="tname" property="name"></result>
</association>
</resultMap>
8.2 一对多
按结果嵌套处理
//学生实体类
public class Student {
private int id;
private String name;
private int tid;
}
//教师实体类:添加一个学生的集合
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
/*注:当使用一对多时,8.1在Student实体类中使用的关联应该去除*/
public interface TeaMapper {
//查询指定教师的所有学生集合
Teacher getAllUser(@Param("tid") int id);
}
<!--两个表有相同列名的,尽量一一列出取别名-->
<select id="getAllUser" parameterType="_int" resultMap="ST">
select s.id sid,s.name sname,t.name tname,t.id tid
from student s,teacher t
where s.Tid=t.id and t.id=#{tid}
</select>
<resultMap id="ST" type="Teacher">
<result column="tid" property="id"></result>
<result column="tname" property="name"></result>
<collection property="students" ofType="Student">
<result column="sid" property="id"></result>
<result column="sname" property="name"></result>
<result column="Tid" property="tid"></result>
</collection>
</resultMap>
8.3 小结
1)关联 -association【多对一】
2)集合 -collection【一对多】
3)javaType & ofType
- javaType:用来指定实体类中属性pojo类型
- ofType:用来指定实体类中集合的泛型pojo类型
九.动态SQL
本质就是根据不同情况,拼接SQL
数据库:
9.1 IF
public interface BlogMapper {
//输入标题或作者查询对应博客,否则查询全部
List<Blog> queryBlogIF(Map map);
}
<select id="queryBlogIF" resultType="Blog" parameterType="map">
select * from blog where 1=1
<if test="title != null">
and title=#{title}
</if>
<if test="author != null">
and author=#{author}
</if>
</select>
9. 2 trim(where\set)
where
- 只会在子元素返回任何内容的情况下才插入 “WHERE” 子句
- 若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除
如上SQL可改装为:
<!--如果只有author,会自动去除开头的and-->
<select id="queryBlogIF" resultType="Blog" parameterType="map">
select * from blog
<where>
<if test="title != null">title=#{title}</if>
<if test="author != null">and author=#{author}</if>
</where>
</select>
set用于动态更新语句
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updataFn" parameterType="map">
update blog
<set>
<if test="title!=null">title=#{title},</if>
<if test="views!=null">views=#{views},</if>
</set>
where id=#{id}
</update>
trim
<!--where元素等价于-->
<trim prefix="WHERE" prefixOverrides="AND |OR ">...</trim>
<!--set元素等价于-->
<trim prefix="SET" suffixOverrides=",">...</trim>
9. 3 choose
choose (when , otherwise):不想使用所有的条件,而只是想从多个条件中选择一个使用。类似于switch语句
<!--若有title,则不管走其他选项;若没有title与author,则执行otherwise-->
<select id="queryBlogIF" resultType="Blog" parameterType="map">
select * from blog where
<choose>
<when test="title != null">
title=#{title}
</when>
<when test="author != null">
author=#{author}
</when>
<otherwise>author="宋应星"</otherwise>
</choose>
</select>
9. 4 foreach
//forEach
List<Blog> queryBlogEc(@Param("lis") List lis);
<select id="queryBlogEc" resultType="Blog" parameterType="list">
select * from blog
<where>
id in
<!--foreach:集合列表,元素名称,开始拼接符号,分隔符,结束拼接符号-->
<foreach collection="lis" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</where>
</select>
<!--等价于:select * from blog where id in('GZ01','GZ02')-->
9. 5 拓展
- 提取公共 sql 片段
<!--提取部分尽量简洁可复用-->
<sql id="abcName">
<if test="title != null">and title=#{title}</if>
<if test="author != null">and author=#{author}</if>
</sql>
<select id="queryBlogIF" resultType="Blog" parameterType="map">
select * from blog
<where>
<include refid="abcName"></include>
</where>
</select>
十.缓存
10.1 一级缓存
- SqlSession级,也称为本地缓存(会话创建到关闭之间有效)
- 之后需要查询相同的数据时,则直接在本地获取(地址相同)
- 每次增、删、改都会更新缓存
- 手动清理缓存 SqlSession.clearCache();
10.2 二级缓存
- 基于 namespace 级别缓存(接口)
- 不同的 mapper 使用不同的缓存
- 手动开启,添加 标签
- 原理:会话关闭时,将一级缓存中的缓存信息保存到二级缓存
【使用步骤】
- 在配置中显示的开启缓存
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
-
在需要开启缓存mapper文件下添加配置
<cache/>
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <!--配置创建了一个FIFO缓存,每隔60秒刷新,最多可以存储结果对象或列表的512个引用,返回的对象是只读的-->
清除策略
LRU
– 最近最少使用:移除最长时间不被使用的对象FIFO
– 先进先出:按对象进入缓存的顺序来移除它们 -
实体类添加序列化 ,即实现接口
implements Serializable
-
对于例如频繁改动的数据,可手动关闭缓存
useCache="false"
(select元素特有属性)
【缓存执行顺序】
- 开始时先查二级缓存
- 二级缓存没有,则查一级缓存
- 一级缓存也没有,最近查数据库