MyBatis 介绍
MyBatis 是一款优秀的持久层框架,用于简化 JDBC 开发。
持久层:
是负责将数据到保存到数据库的那一层代码。即,操作数据库的Java代码为持久层。
而Mybatis就是对JDBC代码进行了封装。
持久层是JavaEE三层架构中的一层。
JavaEE三层架构:表现层、业务层、持久层。
框架:
- 框架就是一个半成品软件,是一套可重用的、通用的、软件基础代码模型。
- 使用框架的好处:在框架的基础之上构建软件编写更加高效、规范、通用、可扩展。
使用JDBC存在以下问题:
-
硬编码
手动注册驱动、获取连接、SQL语句等。
-
操作繁琐
手动设置参数、封装结果集等。
使用Mybatis:
- 硬编码可以配置到配置文件。
- 操作繁琐的地方Mybatis都自动完成。
配置 Mybatis
使用Maven导入Mybatis。
-
在
pom.xml
配置文件中导入坐标:<dependencies> <!--mybatis 依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql 驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency> </dependencies>
-
编写
mybatis-config.xml
文件:在模块下的
resources
目录下创建Mybatis的配置文件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> <!-- 需要注意配置标签的前后顺序(按照MyBatis官方文档目录中的顺序去定义) --> <!-- 别名或包扫描 --> <typeAliases> <!-- name属性的值是实体类所在包 --> <!-- 包扫描后,别名默认为类名(不区分大小写) --> <!-- 使用别名可以简化映射配置文件中 resultType 属性值的编写 --> <package name="package.name.pojo"/> </typeAliases> <!-- environments:配置数据库的连接环境信息, 可以配置多个environment信息, 通过对应的default属性切换不同的environment --> <environments default="development"> <!-- 可以配置多个<environment> --> <!-- 使用 id 给每段环境起名 --> <!-- 在 <environments> 中使用 default='environment-id' 来指定使用哪儿段配置 --> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 数据库连接信息 注意url、username和password --> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis1?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> <environment id="test"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 数据库连接信息 --> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis2?useSSL=false"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <!-- 加载映射文件 --> <!-- ...... --> <!-- 在下节中讲解 --> </mappers> </configuration>
更多MyBatis配置请查看官方文档:MyBatis文档——配置
使用 Mybatis
-
编写
Mapper
类在
java
目录下创建与pojo
对应的Mapper
接口ClassNameMapper.java
:public interface ClassNameMapper { // 方法 }
-
编写 SQL 映射文件
在模块的
resources
目录下创建与pojo
对应的映射配置文件ClassNameMapper.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 namespace="package.name.mapper"> <!-- 相应的SQL语句(XML映射) --> </mapper>
-
在Myb atis的配置文件
mybatis-config.xml
中加载映射文件:- 直接加载映射文件:
<mappers> <mapper resource="com/abc/mapper/ClassNameMapper.xml"/> </mappers>
使用这种方式加载映射文件,多个映射文件需要定义多个
<mapper>
,过于繁琐。- Mapper代理方式(推荐):
如果
Mapper
接口名称和SQL映射文件名称相同,并在同一目录下,则可以使用包扫描的方式简化SQL映射文件的加载。<mappers> <package name="com.abc.mapper"/> </mappers>
注意:使用Mapper代理方式加载映射文件,
Mapper
接口与其对应的配置文件,它们的路径(包名)要相同。即,在java目录下的
Mapper
接口的包名,要和映射配置文件在resources
下的路径要一致。如
Mapper
接口的包名为com.abc.mapper
,那么其映射配置文件的路径则为com/abc/mapper
。了解
Maven
项目结构,请查看:Maven标准化项目结构
实例
在 Mapper 接口中定义方法,方法名就是SQL映射文件中SQL语句的id,并保持参数类型和返回值类型一致。
例如,为 User
对象定义 selectAll()
和 selectById()
方法:
-
创建
User
表:CREATE DATABASE mybatis; USE mybatis; DROP table IF EXISTS tb_user; CREATE table tb_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20), password VARCHAR(20), gender CHAR(1), addr VARCHAR(30) ); INSERT INTO tb_user VALUES (NULL, 'zhangsan', '123', '男', '北京'); INSERT INTO tb_user VALUES (NULL, '李四', '234', '女', '天津'); INSERT INTO tb_user VALUES (NULL, '王五', '11', '男', '西安');
-
pojo
包下定义User
实体类:package com.linner.pojo; public class User { private Integer id; private String username; private String password; private String gender; private String addr; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } public String getAddr() { return addr; } public void setAddr(String addr) { this.addr = addr; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", gender='" + gender + '\'' + ", addr='" + addr + '\'' + '}'; } }
-
mapper
包下定义UserMapper
:package com.linner.pojo; import com.linner.pojo.User; import org.apache.ibatis.annotations.Select; import java.util.List; public interface UserMapper { List<User> selectAll(); User selectById(int id); // 更多操作接口 }
-
在
resources
目录下创建与UserMapper
包名对应的路径,并添加UserMapper.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.linner.mapper.UserMapper"> <!-- select语句使用<select> id 为对应Mapper类中的方法名 resultType 为对应的实体类, 使用了<typeAliases>包扫描, 省略了包名 --> <select id="selectAll" resultType="user"> SELECT * FROM tb_user; </select> <select id="selectById" resultType="user"> SELECT * FROM tb_user WHERE id = #{id}; </select> <!-- 更多XML映射 --> </mapper>
-
resources
目录下,配置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> <!-- 别名或包扫描 --> <!-- 使用别名可以简化映射配置文件中 resultType 属性值的编写 --> <typeAliases> <package name="com.linner.pojo"/> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <!-- 数据库连接信息 --> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///mybatis?useSSL=false&useServerPerpStmts=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="1234"/> </dataSource> </environment> </environments> <mappers> <!-- Mapper代理方式--> <package name="com.linner.mapper"/> </mappers> </configuration>
-
编写测试类:
package com.linner.test; import com.linner.mapper.UserMapper; import com.linner.pojo.User; 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 org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; public class UserMapperTest { @Test public void testSelectAll() throws IOException { // 1. 加载mybatis的核心配置文件,获取SqlSessionFactory String resource = "./mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2. 获取SqlSession对象,用它来执行SQL SqlSession sqlSession = sqlSessionFactory.openSession(); // 3. 执行sql语句 // 3.1 方式一: // List<User> users = sqlSession.selectList("com.linner.mapper.UserMapper.selectAll"); // 3.2 方式二: // 3.2.1 获取UserMapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 3.2.2 执行对应Mapper对象的方法 List<User> users = userMapper.selectAll(); // 4. 处理数据(模拟) System.out.println(users); // 5. 释放资源(仅需释放SqlSession对象) sqlSession.close(); } @Test public void testSelectById() throws IOException { // 模拟接收参数 int id = 1; // 1. 获取sqlSessionFactory String resource = "./mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 2. 获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 3. 获取Mapper接口的代理对象 UserMapper userMapper = sqlSession.getMapper(UserMapper.class); // 4. 执行方法 User user = userMapper.selectById(id); // 5. 处理数据(模拟) System.out.println(user); // 6. 释放资源 sqlSession.close(); } }
XML 映射器
通过在 XxxMapper.xml
中使用元素来定义各种CRUD操作。
常用的元素如下(按照定义的顺序给出):
<sql>
– 可被其它语句引用的可重用语句块。<insert>
– 映射插入语句。<update>
– 映射更新语句。<delete>
– 映射删除语句。<select>
– 映射查询语句。
Select
查询所有数据
xml
配置文件:
<select id="selectAll" resultType="user">
SELECT *
FROM tb_user
</select>
Mapper
接口方法:
List<User> selectAll();
id
:相应Mapper
接口中的方法名。resultType
:相应Mapper
接口中的返回值类型(可自动封装为对象,不区分大小写)。Mapper
接口返回值:返回一个List<User>
。这个返回值可以是User
、List<User>
或ArrayList<User>
等,MyBatis会根据返回值自动封装。
根据id字段查询
xml
配置文件:
<select id="selectById" resultType="user">
SELECT * FROM tb_user WHERE id = #{id};
</select>
Mapper
接口方法:
User selectById(int id);
<select>
中,#{id}
为相应 Mapper
接口中的参数 id
。#{id}
被称为参数占位符,相当于JDBC中的?
占位符。
多条件动态查询
xml
配置文件:
<select id="selectByCondition" resultType="user">
SELECT *
FROM tb_user
<where>
<if test="username != null and username != '' ">
username = #{username}
</if>
<if test="password != null and password != '' ">
AND password = #{password}
</if>
<if test="gender != null and gender != '' ">
AND gender = #{gender}
</if>
<if test="addr != null and addr != '' ">
AND addr = #{addr}
</if>
</where>
</select>
多条件查询使用了动态SQL<where>
和<if>
。如果仅需查询某部分字段,仅仅使用SQL语句在实现上有难度。而Mybatis就提供了动态SQL方便了我们的实现。
-
<if>
:能根据User
对象的值来决定是否在SQL语句中加入其包含的语句。 -
<where>
:只会在子元素返回任何内容的情况下才插入WHERE
子句。而且,若子句的开头为
AND
或OR
,<where>
元素也会将它们去除。
单条件动态查询
xml
配置文件:
<select id="selectByConditionSingle" resultMap="brandResultMap">
SELECT *
FROM tb_brand
<where><!--使用where标签确保不会出错-->
<choose><!--相当于switch-->
<when test="status != null"><!--相当于case-->
status = #{status}
</when>
<when test="companyName != null and companyName != '' "><!--相当于case-->
company_name like #{companyName}
</when>
<when test="brandName != null and brandName != '' "><!--相当于case-->
brand_name like #{brandName}
</when>
<!--没有条件输入很可能会报错,使用<otherwise>保底-->
<!--如果没有使用<where>,则必须使用<otherwise>-->
<!--<otherwise>
1 = 1
</otherwise>-->
</choose>
</where>
</select>
单条件动态查询使用了<where>
、<choose>
、<when>
和<otherwise>
,它们都是MyBatis提供的动态SQL元素。
<choose>
与<when>
(必须)和<otherwise>
元素配合使用。它会根据子元素<when>
的test
属性来判断要选择哪个<when>
或<otherwise>
元素。
如果<choose>
外没有被<where>
包裹则必须使用<otherwise>
来包裹一个永真的条件SQL语句,以确保SQL语句不会出错。如果被<where>
包裹了,<where>
会根据需要判断是否要添加WHERE
子句。
<choose>
、<when>
和<otherwise>
与switch
语句的作用十分相似。
Insert
xml
配置文件:
<insert id="add" useGeneratedKeys="true" keyProperty="id">
INSERT INTO tb_user (username, password, ordered, gender,
addr)
VALUES (#{username}, #{password}, #{ordered}, #{gender},
#{addr});
</insert>
Mapper
接口方法:
boolean add(User user);
接口方法直接传入对象即可,对象成员要与 VALUES
子句中的参数一一对应(参数符号中的名称要与对象的成员名称相同)。
如果XML映射中只有一个参数,那么这个参数的名称不必与接口的参数名称相同。
-
id
:含义与<insert>
的含义相同,为相应Mapper
接口中的方法名(以下id
均为此含义,省略)。 -
useGeneratedKeys
:- 值为
true
时,Mybatis 会使用 JDBC 的getGeneratedKeys
方法来取出由数据库内部生成的主键(如自增的主键)。 - 默认为
false
。
- 值为
-
keyProperty
:getGeneratedKeys
获取到的主键值所要赋予的对象成员名。MyBatis 会使用getGeneratedKeys
的返回值来设置它的值。如,
keyProperty="id"
——直接将获取到的主键值赋值给user.id
。如果生成列不止一个,可以用逗号分隔多个属性名称。
-
Mapper
接口返回值:boolean
,插入成功返回true
,插入失败返回false
。返回值也可以选择忽略,将
add()
接口的返回值设置为void
即可。
Update
xml
配置文件:
<update id="update">
UPDATE tb_user
<set>
<if test="username != null and username != '' ">
username = #{username},
</if>
<if test="password != null and password != '' ">
password = #{password},
</if>
<if test="ordered != null">
ordered = #{ordered},
</if>
<if test="gender != null and (gender == '男' or gender == '女')">
gender = #{gender},
</if>
<if test="addr != null and addr != ''">
addr = #{addr}
</if>
</set>
WHERE id = #{id};
</update>
Mapper
接口方法:
int update(User user);
在 <update>
中有一些用 <set>
包裹起来的 <if>
。这是因为需要使用 <set>
动态包含需要更新的列,忽略其它不更新的列。从而能根据User
对象的值来决定要更新哪些数据,并且动态地改变SQL语句。
Update的Mapper接口返回值是int
类型,返回更新的行数。同样可以将接口的返回值设置为void
来忽略它。
Delete
删除单行数据
xml
配置文件:
<delete id="deleteById">
DELETE
FROM tb_user
WHERE id = #{id};
</delete>
Mapper
接口方法:
int deleteById(int id);
删除多行数据
xml
配置文件:
<delete id="deleteByIds">
DELETE
FROM tb_brand
WHERE id
IN
<!--
<foreach>参数:
- item: 代表数组参数中的每个元素
- separator: 分隔符
- open: 插入开始符
- close: 插入结束符
-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
Mapper
接口方法:
int deleteByIds(@Param("ids") int[] ids);
Mybatis提供了 <foreach>
标签遍历数组,拼接SQL语句。<foreach>
同样也是动态SQL。
更多XML映射器请查看官方文档:MyBatis文档——XML映射器
结果映射
resultMap
假设SQL表中的字段与实体类的成员变量名无法一一对应,会导致SQL语句的传参出现问题。例如:
CREATE table tb_user (
user_id INT PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(20),
user_password VARCHAR(20),
user_gender CHAR(1),
user_addr VARCHAR(30)
);
public class User {
private Integer id;
private String username;
private String password;
private String gender;
private String addr;
// setter 和 getter
// ...
}
那么可以使用<resultMap>
元素来对表字段和成员名做一个映射(结果映射):
<!--
<resultMap>:
- id: 唯一标识
- type: 映射的类型,支持别名
-->
<resultMap id="userResultMap" type="user">
<!--
两种标签:
- <id>: 完成主键字段的映射
- column: 表的列名
- property: 实体类的属性名
- <result>: 完成一般字段的映射
- column: 表的列名
- property: 实体类的属性名
-->
<id column="user_id" property="id">
<result column="user_name" property="userame"/>
<result column="user_password" property="password"/>
<result column="user_gender" property="gender"/>
<result column="user_addr" property="addr"/>
</resultMap>
<select id="selectAll" resultMap="userResultMap">
SELECT *
FROM tb_user
</select>
要使用结果映射,需要把
<select>
中的resultType
属性替换为resultMap
,并且其属性值为<resultMap>
的id
值。
自动映射
当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。
接上节例子,使用SQL语句的 AS
来实现:
<select id="selectAll" resultType="user">
SELECT
user_id AS "id",
user_name AS "username",
user_password AS "password",
user_gender AS gender,
user_addr AS "addr"
FROM tb_user
</select>
通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将
mapUnderscoreToCamelCase
设置为true
。<!-- 配置mybatis自动转换为驼峰式命名 --> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>
<resultMap>
和自动映射可以混用。
参数
MyBatis有两种参数:
#{p}
—— 会自动转义。${p}
—— 不会自动转义。
官方文档:MyBatis——XML映射器参数
MyBatis 参数封装:
-
单个参数:
-
POJO类型: 直接使用,保证 属性名 和 参数占位符名称 一致
-
Map类型: 直接使用,保证 键名 和 参数占位符名称 一致
-
Collection: 封装为Map集合
相当于:
map.put("arg0", collection集合) map.put("collection", collection集合)
- 使用
@Param
注解,替换Map集合中默认的arg键名
- 使用
-
List: 封装为Map集合
相当于:
map.put("arg0", list集合) map.put("collection", list集合) map.put("list", list集合)
- 使用
@Param
注解,替换Map集合中默认的arg键名
- 使用
-
Array: 封装为Map集合
相当于:
map.put("arg0", 数组) map.put("array", 数组)
- 使用
@Param
注解,替换Map集合中默认的arg键名
- 使用
-
其他类型: 直接使用,且占位符名称和参数名称可以不相同
-
-
多个参数: 封装为Map集合
-
每个参数有两个键:
相当于:
map.put("arg0", 参数值1) map.put("param1", 参数值1) map.put("arg1", 参数值2) map.put("param2", 参数值2)
-
使用
@Param
注解,替换Map集合中默认的arg键名:相当于:
@Param("username") 参数类型 参数名 map.put("username", 参数值1) map.put("param1", 参数值1)
-
示例:
public interface UserMapper {
User selectById(int id);
List<User> selectByCondition(
@Param("username") String username,
@Param("password") String password);
}
注解实现CRUD
对于简单的SQL语句来说,使用注解开发会比配置文件开发更加方便。
@Select(value = "SELECT * FROM tb_user WHERE id = #{id}")
User selectById(int id);
注意:注解是用来替换映射配置文件方式配置的,所以使用了注解,就不需要再映射配置文件中书写对应的
statement
。
Mybatis 针对 CURD 操作都提供了对应的注解:
- 查询 :
@Select
- 添加 :
@Insert
- 修改 :
@Update
- 删除 :
@Delete
注解适合用于完成简单功能,而使用配置文件来完成复杂功能。如果使用注解来完成动态SQL之类的复杂功能,就需要使用到MyBatis提供的SQL构建器来完成。详情请阅读官方SQL构建器文档:MyBatis文档——SQL语句构建器。
动态SQL
动态 SQL 是 MyBatis 的强大特性之一。
MyBatis提供的动态SQL元素有:
<if>
<choose>
(<when>
,<otherwise>
)<trim>
(<where>
,<set>
)<foreach>
更多与动态SQL请查看官方文档:MyBatis文档——动态SQL
SqlSessionFactory工具类抽取
MyBatis重复代码会造成一些问题:
- 不利于后期的维护。
- SqlSessionFactory工厂类进行重复创建。
对于Mybatis的基础操作出现的重复代码,可以使用一个静态代码块来自动加载:
public class SqlSessionFactoryUtils {
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();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
工具类抽取以后,以后在对Mybatis的SqlSession进行操作的时候,就可以直接使用:
SqlSessionFactory sf = SqlSessionFactoryUtils.getSqlSessionFactory();
评论