概述
什么是MyBatis:MyBatis 是一个半 ORM 框架,它内部封装了 JDBC 的大部分操作,在开发的时候我们只需要关注 SQL 语法本身,而不需要花精力去处理 JDBC 的一些操作。在 MyBatis 中通过 xml 或者注解的方式,将实体类与数据库中的表进行映射,由框架执行 SQL 并将执行结果映射为实体类返回
MyBatis 的优点:
-
相比于 JDBC,减少了大量的代码,不需要我们手动开启连接、关闭连接
-
解耦。将 SQL 写在 xml 文件中,使 SQL 与程序代码分离,便于我们直接进行管理
-
支持动态 SQL
-
能够与 Spring 很好的集成,与其他数据库兼容
MyBatis 的缺点:
- 当表中的字段多,表的关联多的时候,SQL 的编写量上升
- SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
#{} 与 ${} 的区别
- #是预编译处理,$是字符串替换
- 在处理#时,会将括号中的值用?替换,并调用 PreparedStatement 的 set 方法来赋值
- 在处理&时,把${}替换成变量的值
- 使用#可以有效的防止 SQL 注入,提高系统安全性
快速开始
- 创建配置文件 mybatis-config.xml,存放于类路径下
<?xml version="1.0" encoding="UTF8" ?>
<!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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/dbname?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="" />
</mappers>
</configuration>
- 创建 SQL 映射文件 EmployeeDao.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wes.dao.EmployeeDao">
<!--查询操作必须指定 resultType,增删改可以不写-->
<select id="getEmployeeById" resultType="com.wes.pojo.Employee">
select * from mybatis.employee where id = #{id}
</select>
</mapper>
- 测试使用
public static void main(String[] args) {
String resource = "mybatis-config.xml";
// 读取配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
// SqlSessionFactoryBuilder -> SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取和数据库的一次会话
// openSession(true):自动提交
SqlSession sqlSession = sqlSessionFactory.openSession();
// 通过会话,操作数据库
// 这里获取到 dao 接口的实现,获取到的是接口的代理对象,这个对象是mybatis自动创建的
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
Employee employee = mapper.getEmployeeById(1);
}
每一个 MyBatis 应用都是以一个 SQLSessionFactory 的实例为核心的,该实例通过 SqlSessionFactoryBuilder 获得
而 SqlSessionFactoryBuilder 可以从 xml 或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例
从 sqlSessionFactory 获取的 SqlSession 完全包含了面向数据库执行 SQL 命令所需要的所有方法,可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
全局配置文件
全局配置文件 mybatis-config.xml
properties
作用:引入外部配置文件
<properties resource="" /> 从类路径下开始引用
<properties url="" /> 引用磁盘路径或网路路径的资源
演示:
username=root
password=1234
url=jdbc:mysql://localhost:3306/mybatis
driverClassName= com.mysql.cj.jdbc.Driver
<?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>
<properties resource="db.properties">
<!--可以添加属性,优先使用外部文件-->
<property name="username" value="" />
</properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="EmployeeDao.xml" />
</mappers>
</configuration>
settings
改变 MyBatis 的运行时行为
所有设置:https://mybatis.org/mybatis-3/zh/configuration.html#settings
<?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>
<properties resource="db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启驼峰命名自动映射: A_COLUMN -> aColumn(pojo)-->
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="EmployeeDao.xml" />
</mappers>
</configuration>
typeAliases
作用:为常用的 Java 类名起别名(不推荐使用)
<select id="getEmployeeById" resultType="employee">
select * from mybatis.employee where id = #{id}
</select>
<typeAliases>
<!--默认为类名,不区分大小写;也可以使用alias自定义-->
<typeAlias type="com.wes.pojo.Employee" alias="employee" />
<!--批量起别名,默认别名就是类名,不区分大小写-->
<package name="com.wes.pojo" />
</typeAliases>
当使用了 package 还想给某个特定类起别名,则在类的上面添加注解 @Alias(“ ”)
environments
<environments default="development">
<!--environment配置一个具体的环境,都需要一个事务管理器和一个数据源-->
<environment id="development">
<!--事务管理器有两种:JDBC、MANAGED-->
<transactionManager type="JDBC"/>
<!--POOLED:使用连接池技术-->
<dataSource type="POOLED">
<property name="driver" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
mappers
作用:注册绑定 SQL 映射文件
<mappers>
<mapper resource="EmployeeDao.xml" /> 在类路径找映射文件
<mapper class="com.wes.dao.EmployeeDao"/> 全类名
<mapper url="" /> 从磁盘或网络路径中引用
<package name="com.wes.pojo" /> 批量注册
</mappers>
映射时,映射文件名与接口名要一致,且在同个包目录下;或者在 resources 中创建同结构包
SQL 映射文件
MyBatis 中常用的标签有:insert、delete、update、select;resultMap、sql(抽取 SQL)、cache、cache-ref
CRUD
resultType:select 必须指定,其他可不必要
parameterType:参数类型。可选,默认不写
<!--查询操作必须指定 resultType返回类型,增删改可以不写-->
<select id="..." resultType="com.wes.pojo.Employee">
select * from emp
</select>
<insert id="..">
insert into emp value (#{id}, #{name}, #{gender}, #{email})
</insert>
<delete id="..">
delete from emp where id = #{id}
</delete>
<update id="..">
update emp set name = #{name} where id = #{id}
</update>
对于增删改操作,需要开启事务
SqlSession sqlSession = sqlSessionFactory.openSession(true);
获取主键
useGeneratedKeys:取出数据库生成的主键,仅对 insert、update 有效
keyProperty:实体类中的主键字段,唯一标记一个属性,仅对 insert、update 有效
<insert id=".." useGeneratedKeys="true" keyProperty="id">
insert into emp value (null, #{name}, #{gender})
</insert>
@Test
public void test() throws IOException {
Emp emp = new Emp(null, "wes", 0);
int id = emp.getId();
System.out.println("插入的 id :" + employee.getId());
}
模糊查询
- 在 Java 代码层面,使用通配符传递参数
- 在映射文件中,使用通配符
List<User> userList = mapper.getUserLike("%月%");
<select id=".." resultType="Product">
select * from user where name like concat('%', '#{name}', '%')
</select>
分页
select * from user limit startIndex, pageSize;
select * from user limit 0, 2; -- 每页显示2个
select * from user limit 3; -- 显示前三个数据
创建一个 pageBean 实体类,在类中维护 startIndex,pageSize 两个属性,用来表示开始位置和每页记录数
<select id=".." resultMap="User">
select * from user limit #{startIndex}, #{pageSize}
</select>
HashMap<String, Integer> map = new HashMap<>();
map.put("startIndex", 0);
map.put("pageSize", 2);
List<User> list = mapper.getUserByLimit(map);
其他实现:RowBounds 实现分页,通过 Java 代码层面实现分页。通过第三方插件实现(例如:PageHelper)
参数传入
从接口中传入不同个数的参数进入映射中
- 单个:直接传入
- 多个:使用 @Param(" ") 定义参数名
如何不使用该注解显示定义参数名,会报错 Parameter ‘id’ not found. Available parameters are [arg1, arg0, param1, param2]
<select id=".." resultType="">
select * from emp where id = #{arg0} and name = #{arg1}
select * from employee where id = #{param1} and name = #{param2}
</select>
- 当传入参数既有基本类型,又有对象时:
int addEmployee(@Param("id") Integer id, Employee employee);
参数获取
在映射文件中,使用 #{} 获取参数
- 当传入的参数只有一个:括号中的值任意(#{123})
- 当传入的参数不止一个:
- 当传入的是实体:#{对象名.属性}
- 当传入的是 Map:#{key}
参数返回
除了返回 int、对象之外,SQL 中还可以返回 Map 给 DAO 层
Map<String, Object> method();
<select id="method" resultType="map">
select * from emp
</select>
获取到的 Map 以特定字段名为 key,指定值为 value
@MapKey("id") // 以 id 为 key
Map<Integer, Employee> method();
<select id="method" resultType="Employee">
select * from emp
</select>
Map<String, Emp> map = mapper.method();
Emp emp = map.get(1);
ResultMap
ResultMap:结果集映射
使用 ResultMap 可以自定义封装规则,解决属性名、数据库与实体类字段名不一致问题
字段名不一致问题:起别名 / 结果集映射
<select id="getUserById" resultType="User" parameterType="int">
select id, name, psw as password from user where id = #{id};
</select>
<select id="getUserById" resultMap="UserMap" parameterType="int">
select * from user where id = #{id};
</select>
<resultMap id="UserMap" type="user">
<id property="id" column="id" />
<result column="psw" property="password" />
</resultMap>
- column:数据库字段
- property:实体类字段
association
在 ResultMap 中,可以使用 association 来关联其他对象
子查询方式:
<select id="query1" resultMap="Student_Teacher">
select * from student
</select>
<select id="query2" resultType="Teacher">
select * from teacher where id = #{id}
</select>
<resultMap id="Student_Teacher" type="Student">
<!--property:实体类字段;column:数据库字段-->
<result property="id" column="id" />
<result property="name" column="name" />
<!--javaType:指定这个属性的类型 column:传入 tid 给 子查询-->
<association property="teacher" column="tid" javaType="Teacher" select="query2"/>
</resultMap>
连表查询:
<select id="query" resultMap="Student_Teacher">
select s.id sid, s.name sname, t.id tid t.name tname
from student s join teacher t on s.tid = t.id
</select>
<resultMap id="Student_Teacher" type="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<association property="teacher" javaType="Teacher">
<result property="id" column="tid" />
<result property="name" column="tname" />
</association>
</resultMap>
public class Student {
private int id;
private String name;
private Teacher teacher;
}
collection
使用 collection 做集合
public class Teacher {
private int tid;
private String tname;
private List<Student> students;
}
public class Student {
private int sid;
private String sname;
private int tid;
}
子查询:
<select id="query1" resultMap="Teacher_Student">
select * from teacher where tid = #{tid}
</select>
<select id="query2" resultType="Student">
select * from student where tid = #{tid}
</select>
<resultMap id="Teacher_Student" type="Teacher">
<!--javaType:集合类型, ofType:集合属性类型-->
<collection property="students" column="tid" javaType="ArrayList" ofType="Student" select="query2" />
</resultMap>
联表查询:
<select id="queryTeacher" resultMap="Teacher_Student">
select t.tid, t.tname, s.sid, s.sname
from student s join teacher t on t.tid = s.tid
</select>
<resultMap id="Teacher_Student" type="Teacher">
<result property="tid" column="tid" />
<result property="tname" column="tname" />
<collection property="students" ofType="Student" >
<result property="sid" column="sid" />
<result property="sname" column="sname" />
<result property="tid" column="tid" />
</collection>
</resultMap>
日志工厂
作用:使控制台输出日志信息
常用:LOG4J、STDOUT_LOGGING
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
建立 log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地
log4j.rootLogger=debug,console,file
#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 = [%c]-%m%n
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./log/yue.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
动态SQL
IF
<select id=".." resultType="blog">
select * from blog
<where>
<if test="title != null">
and title = #{title}
</if>
</where>
</select>
choose
choose:多选一,类似于 switch
<select id="..." resultType="Blog">
SELECT * FROM BLOG WHERE
<where>
<choose>
<when test="title != null and !title.equals("")">
title = #{title}
</when>
<otherwise>
AND views = #{views}
</otherwise>
</choose>
</where>
</select>
set
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author},
</if>
</set>
<where>
<if test="id != null">
id = #{id}
</if>
</where>
</update>
trim
where、set 的自定义:trim
<select id="..." resultType="Blog">
<!--prefix:前缀, siffix:后缀-->
<trim prefix="WHERE" prefixOverrides="AND |OR" siffix="and">
<if test="state != null">
state = #{state} and
</if>
<if test="title != null">
AND title like #{title} and
</if>
</trim>
</select>
<trim prefix="SET" suffixOverrides=","> ...</trim>
foreach
作用:对一个集合进行遍历,通常是在构建 IN 条件语句的时候
select * from blog where 1 = 1 and (id = 1 or id = 2 or id = 3)
<select id=".." resultType="blog">
select * from blog
<where>
<if test="ids.size > 0">
<!--separator:元素之间的分隔符-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</if>
</where>
</select>
HashMap map = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids", ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
SQL
作用:把一些功能的部分抽取出来,方便复用
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author">
and author = #{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="blog">
select * from blog
<where>
<include refid="if-title-author"></include>
</where>
</select>
缓存
MyBatis 的两种缓存:一级缓存、二级缓存
默认开启一级缓存(本地缓存),一级缓存是线程级别的缓存,SqlSession 级别的缓存,只在一个 sqlSession 中有效
清除缓存:sqlSession.clearCache()
二级缓存是全局范围的缓存,在 SqlSession 关闭或提交后才会生效
<settin name="cacheEnabled" value="true" />
在需要使用二级缓存的映射文件出使用 cache 配置缓存 <cache />
原理:一级与二级不会存在同一个数据,先看二级,再看一次
注解
- 使用注解之前需要先打开自动提交事务
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);
}
- 在接口中使用注解
@Select("select * from user")
List<User> getUsers();
@Insert("insert into user(id, name, psw) values (null, #{name}, #{password})")
int addUser(User user);
@Update("update user set name=#{name}, psw=#{password} where id = #{id}")
int updateUser(User user);
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id") int id);