I. 简介
1. MyBatis是什么
MyBatis是一款优秀的持久层框架。其通过使用简单的XML(或注解)和映射原生信息来取代了几乎所有的JDBC代码、手动设置参数和获取结果集的过程。为了理解什么是持久层框架,我们需要首先了解什么是持久化以及持久层。
持久化
持久化是将程序数据在持久状态和瞬时状态间转化的机制。也就是说将数据从RAM(内存)中保存到ROM(存储设备比如磁盘)中。常见的持久化机制如JDBC, IO等。持久化的本质类似于将鲜肉冷藏,吃的时候拿出来解冻。
持久层
持久层是完成持久化工作的代码块(DAO层[Data Access Object, 数据访问对象])。数据持久化意味着将内存中的数据保存到磁盘加以固化,而持久化的实现过程则大多通过各种关系型数据库完成。
在我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现,也就是我们的持久层。(操作数据库的层)
2. 为什么要使用MyBatis
如上所述,MyBatis是一款用于持久层的ORM框架(Object Relationship Mapping, 对象关系映射)。框架的使用其目的往往是避免繁琐的重复代码块,减少程序员的工作量,从而提高开发效率。MyBatis也是一样,使用它可以完全代替JDBC来实现持久层的所有功能。
MyBatis的优点
使用的人多
*简单易学*。不需要任何第三方依赖,只需要安装两个jar文件并配置几个SQL映射文件就好。通过文档和原代码,可以很好的掌握其设计思路和实现
*灵活*。myBatis不会对应用冲虚或者数据库的现有设计强加任何影响。SQL写在XML里,便于统一管理和优化
*解除SQL与程序代码的耦合*。通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更以维护和做单元测试
提供XML标签,支持编写动态SQL
…
3. 文章概述
在II章节中本文会首先介绍MyBatis的工作原理。接着在III章节中介绍MyBatis项目的开发流程。第四章中会详细介绍MyBatis核心配置文件。随后的第五章节会讲解如何编写配置文件。第六章节会介绍更为简洁的注解开发。第七章介绍关系型数据库中常见的多对一、一对多处理。之后的两个章节分别介绍动态SQL和缓存。
本文环境说明:
- JDK 8+
- MySQL 5.7.19
- Maven-3.6.0
- IDEA
学习前需要掌握:
- JDBC
- MySQL
- Java基础
- Maven
- JUnit
II. Mybatis工作原理
III. Mybatis项目开发流程
1. 准备工作
a. 搭建Maven环境
这一步没有什么好说的,用Idea自动生成Maven项目即可。
b. 导入MyBatis依赖
找到MyBatis依赖,并配置到pom.xml中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
c. 编写核心配置文件
编写核心配置文件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>
<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://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/kuang/dao/userMapper.xml"/>
</mappers>
</configuration>
d. 编写MyBatis工具类
类似于JDBC中的工具类:
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 java.io.IOException;
import java.io.InputStream;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
// 使用Mybatis获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取SqlSession连接, 类似JDBC的Connection对象
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}
2. 编写一个Mybatis功能
a. 创建实体类
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// 可以使用lombok来简化代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密码
//构造,有参,无参 //set/get //toString()
}
b. 编写Mapper接口
Mapper接口相当于JDBC中DAO层的接口,用于定义操作数据库的方法:
import com.kuang.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectUser();
}
c. 编写Mapper.xml配置文件
Mapper.xml文件相当于JDBC中DAO层的接口实现类,用于定义实际操作数据库的配置:
<?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.kuang.dao.UserMapper">
<select id="selectUser" resultType="com.kuang.pojo.User">
select * from user
</select>
</mapper>
Note: namespace对应DAO层接口,非常重要,不能写错。
d. 编写JUnit单元测试
public class MyTest {
@Test
public void selectUser() {
SqlSession session = MybatisUtils.getSession();
//方法一:
//List<User> users = session.selectList("com.kuang.mapper.UserMapper.selectUser");
//方法二:
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
for (User user: users){
System.out.println(user);
}
session.close();
}
}
e. 运行测试
IV. 核心配置文件详解
mybatis-config.xml是myBatis项目中的核心配置文件,下面是模板:
<?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"></properties>
<typeAliases>
<package name="com.soul.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/> <!--默认JDBC, type = JDBC | MANAGED-->
<dataSource type="POOLED"> <!--数据库连接池 type= UNPOOLED| POOLED -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
<!--第二个环境,想切就换environments的default-->
<environment id="test">
<transactionManager type=""></transactionManager>
<dataSource type=""></dataSource>
</environment>
</environments>
<!--绑定接口-->
<mappers>
<mapper class="com.soul.dao.TeacherMapper"/>
<mapper class="com.soul.dao.StudentMapper"/>
</mappers>
</configuration>
Note: 配置文件的元素顺序不能错!!!
mybatis-config.xml主要元素包括:
environments
- 配置MyBatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行 环境(通过default指定)
- 子元素节点:environment 具体的一套环境,通过设置id进行区别,id保证唯一!
properties
数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过
properties 元素的子元素来传递:
- 第一步 ; 在资源目录下新建一个db.properties:
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis? useSSL=true&useUnicode=true&characterEncoding=utf8 username=root password=123456
- 第二步 : 将文件导入properties 配置文件
<!--导入properties文件--> <properties resource="db.properties"/>
mappers
mappers中可定义多个映射器mapper,即映射SQL语句的文件,其目的是为了告诉MyBatis到哪里能够找到这些mapper文件。
MyBatis的真正强大在于它的映射语句,这是它的魔力所在。由于它的异常强大,映射器的XML文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis为聚焦于 SQL而构建,以尽可能地为你减少麻烦。
<!-- 引用资源的各种方式 --> <!-- 使用相对于类路径的资源引用 --> <mappers> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers> <!-- 使用完全限定资源定位符(URL) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> </mappers> <!-- 使用映射器接口实现类的完全限定类名 需要配置文件名称和接口名称一致,并且位于同一目录下 --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> </mappers> <!-- 将包内的映射器接口实现全部注册为映射器 但是需要配置文件名称和接口名称一致,并且位于同一目录下 --> <mappers> <package name="org.mybatis.builder"/> </mappers>
typeAliases
类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。
<!--配置别名,注意顺序--> <typeAliases> <!--当这样配置时, User 可以用在任何使用 com.kuang.pojo.User 的地方。--> <typeAlias type="com.kuang.pojo.User" alias="User"/> </typeAliases> <!-- 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean --> <!-- 每一个在包 com.kuang.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的 非限定类名来作为它的别名。 --> <typeAliases> <package name="com.kuang.pojo"/> </typeAliases>
settings
可以设置各种设置。在这里介绍一种使用Log4j格式的log用于Debug:
Log4j
使用步骤:
第一步:导入log4j包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
第二步:配置文件log4j.properties编写
#将等级为DEBUG的日志信息输出到console和file这两个目的地,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/soul.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
第三步:setting设置日志实现:
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
第四步:在程序中使用Log4j进行输出:
@Test
public void testLog4j() {
logger.info("info: 进入了testLog4j方法");
logger.debug("debug: 进入了testLog4j方法");
logger.error("error: 进入了testLog4j方法");
}
V. 配置文件
1. CRUD操作
a. select
select语句有很多属性可以详细配置每一条SQL语句:
- id
- 命名空间中唯一的标识符
- 接口中的方法名与映射文件中的SQL语句ID 一一对应
- parameterType
- 传入SQL语句的参数类型 。【万能的Map,可以多尝试使用】
- resultType
- SQL语句返回值类型。【完整的类名或者别名】
b. insert
c. update
d. delete
具体流程
第一步:编写DAO层接口中的方法:
package com.soul.dao;
import com.soul.pojo.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
// 获取全部用户
List<User> getUserList();
// 根据ID查询用户
User getUserById(int id);
User getUserByMap(Map<String, Object> map);
// insert一个用户
int addUser(User user);
// 用Mapinsert用户
int addUserByMap(Map<String, Object> map);
int updateUser(User user);
int deleteById(int id);
}
第二步:编写Mapper.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=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.soul.dao.UserMapper">
<!--id 对应方法名-->
<select id="getUserList" resultType="hello">
select * from user;
</select>
<select id="getUserById" parameterType="int" resultType="User">
select * from user where id = #{id};
</select>
<select id="getUserByMap" parameterType="map" resultType="User" >
select * from user where id = #{id} and name = #{name};
</select>
<!--属性的对象可以直接取出-->
<insert id="addUser" parameterType="User">
insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd});
</insert>
<insert id="addUserByMap" parameterType="map">
insert into user (id, name, pwd) values (#{userId}, #{userName}, #{userPwd});
</insert>
<update id="updateUser" parameterType="User">
update user set name = #{name}, pwd=#{pwd} where id = #{id};
</update>
<delete id="deleteById" parameterType="int">
delete from user where id = #{id}
</delete>
</mapper>
第三步:在mybatis-config.properties中配置mapper:
<!--每一个Mapper.xml都需要在Mybatis核心配置文件中注册!-->
<mappers>
<mapper resource="com/soul/dao/UserMapper.xml"/>
</mappers>
第四步:编写JUnit测试类测试:
@Test
public void testGetUserById() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserById(1);
System.out.println(user);
sqlSession.close();
}
@Test
public void testGetUserByMap() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("id", 3);
map.put("name", "李四");
User user = userMapper.getUserByMap(map);
System.out.println(user);
sqlSession.close();
}
@Test
public void testAddUser() {
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User(4, "小红", "123");
int res = userMapper.addUser(user);
if (res > 0) {
System.out.println("插入成功");
} else {
System.out.println("插入失败");
}
// 增删改需要提交事务
sqlSession.commit();
sqlSession.close();
}
2. ResultMap
当数据库中返回的结果对应的是Java中的一个对象时,使用ResultMap可以避免Field对应不上从而导致值为空的情况。ResultMap用来指定返回结果的类型和格式。
a. 自动映射
- ResultMap元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来
- 实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同 等功能的长达数千行的代码
- ResultMap 的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语 句只需要描述它们的关系就行了
<select id="selectUserById" resultType="map">
select id , name , pwd
from user
where id = #{id}
</select>
上述语句只是简单地将所有的列映射到 HashMap 的键上,这由 resultType 属性指定。虽然在 大部分情况下都够用,但是 HashMap 不是一个很好的模型。你的程序更可能会使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 对象)作为模型。
b. 手动映射
1). 返回值类型为resultMap:
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
</select>
2) 编写resultMap, 实现手动映射:
<resultMap id="UserMap" type="User">
<!-- id为主键 -->
<id column="id" property="id"/>
<!-- column是数据库表的列名 , property是对应实体类的属性名 -->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
此外,在数据库中,存在一对多,多对一的情况,我们之后会 使用到一些高级的结果集映射,association,collection这些,我们将在之后讲解,今天你们需要把这 些知识都消化掉才是最重要的!理解结果集映射的这个概念!
VI. 注解开发(代替配置文件)
mybatis最初配置信息是基于XML ,映射语句(SQL)也是定义在XML中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,Java注解的的表达力和灵活性十分有限。最强大的MyBatis映 射并不能用注解来构建。
sql 类型主要分成 :
- @select ()
- @update ()
- @Insert ()
- @delete ()
Note: 利用注解开发就不需要mapper.xml映射文件了。
注解开发步骤:
在接口中添加注解
package com.soul.dao; import com.soul.pojo.User; import org.apache.ibatis.annotations.*; import java.util.List; public interface UserMapper { // 获取全部用户 @Select("select * from user") List<User> getUserList(); // 方法存在多个参数,所有参数前面必须加上@Param("var")注解 @Select("select * from user where id = #{id}") User getUserByID(@Param("id") int id, @Param("name") String name); @Insert("insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd});") int addUser(User user); @Update("update user set name = #{name}, pwd = #{pwd} where id = #{id};") int updateUser(User user); @Delete("delete from user where id = #{id};") int deleteUser(@Param("id") int id); }
在MyBatis的核心配置文件中注入Mapper(注入接口)
<!--使用class绑定接口--> <mappers> <mapper class="com.soul.dao.UserMapper"/> </mappers>
JUnit测试
@Test
public void testGetAllUser() {
SqlSession session = MybatisUtils.getSession(); //本质上利用了jvm的动态代理机制
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.getAllUser();
for (User user : users){
System.out.println(user);
}
session.close();
}
关于@Param
@Param注解用于给方法参数起一个名字。以下是总结的使用原则:
- 在方法只接受一个参数的情况下,可以不使用@Param。
- 在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
- 如果参数是 JavaBean , 则不能使用@Param。
- 不使用@Param注解时,参数只能有一个,并且是Javabean。
#与$的区别
- #{} 的作用主要是替换预编译语句(PrepareStatement)中的占位符? 【推荐使用】
INSERT INTO user(name) VALUES (#{name});
INSERT INTO user(name) VALUES (?);
- \${} 的作用是直接进行字符串替换(${} 不安全,无法防止sql注入)
VII.多对一、一对多处理
在调用数据库数据时,我们往往会遇到多对一和一对多的处理,这时候就要在mapper.xml中使用到<association>
和<collection>
标签。我们通过下面老师和学生的例子来详解如何实现此功能。
多对一
- 多个学生对应一个老师。
- 如果对于学生这边,就是一个多对一的现象,即从学生这边关联(association)一个老师!
我们首先编写实体类:
@Data //GET,SET,ToString,有参,无参构造 public class Teacher { private int id; private String name; } @Data public class Student { private int id; private String name; //多个学生可以是同一个老师,即多对一 private Teacher teacher; }
编写实体类对应的Mapper接口
package com.soul.dao; import com.soul.pojo.Student; import java.util.List; public interface StudentMapper { List<Student> getAllStudent(); List<Student> getAllStudent2(); }
package com.soul.dao; import com.soul.pojo.Teacher; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface TeacherMapper { @Select("select * from teacher where id = #{tid};") Teacher getTeacher(@Param("tid") int id); }
编写对应的两个mapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.soul.dao.StudentMapper"> <!--方式一--> <!--思路 1. 查询所有的学生信息 2. 根据查询出来的学生的tid,寻找对应的老师--> <select id="getAllStudent" resultMap="StudentTeacher"> select * from student; </select> <resultMap id="StudentTeacher" type="student"> <result property="id" column="id"/> <result property="name" column="name"/> <!--复杂的实行,我们需要单独处理 对象: association 集合: collection--> <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/> </resultMap> <select id="getTeacher" resultType="teacher"> select * from teacher where id = #{id}; </select> <!--方式二,推荐!!!--> <!--按照结果嵌套处理--> <select id = "getAllStudent2" resultMap="StudentTeacher2"> select s.id as sid, s.name as sname, t.id as tid, t.name as tname from student s, teacher t where s.tid = t.id; </select> <resultMap id="StudentTeacher2" 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> </mapper>
配置mapper, JUnit测试
###一对多
- 一个老师拥有多个学生
- 如果对于老师这边,就是一个一对多的现象,即从一个老师下面拥有一群学生(collection)!
编写实体类:
package com.soul.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class Student { private int id; private String name; private int tid; }
package com.soul.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class Teacher { private int id; private String name; private List<Student> students; }
编写DAO层接口:
package com.soul.dao; import com.soul.pojo.Teacher; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; public interface TeacherMapper { @Select("select * from teacher;") Teacher getAllTeacher(); // 获取指定老师下的所有学生及老师的信息 Teacher getTeacher(@Param("tid") int id); }
编写Mapper.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.soul.dao.TeacherMapper"> <!--按照结果嵌套查询--> <select id="getTeacher" resultMap="TeacherStudent"> select s.id as sid, s.name as sname, t.id as tid, t.name as tname from student s, teacher t where s.tid = t.id and tid = #{tid} </select> <resultMap id="TeacherStudent" type="Teacher"> <result property="id" column="tid"/> <result property="name" column="tid"/> <!--JavaType="" 制定属性的类型 集合中的泛型信息,我们使用ofType获取--> <collection property="students" ofType="Student"> <result property="id" column="sid"/> <result property="name" column="sname"/> <result property="tid" column="tid"/> </collection> </resultMap> </mapper>
配置Mapper,JUnit测试
VIII. 动态SQL
动态SQL指的是根据不同的查询条件 , 生成不同的Sql语句。
官网描述:
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你
就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意 去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
虽然在以前使用动态 SQL 并非一件易事,但正是 MyBatis 供了可以被用在任意 SQL 映射语 句中的强大的动态 SQL 语言得以改进这种情形。
动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有 很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。 MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。
-------------------------------
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
-------------------------------
我们之前写的 SQL 语句都比较简单,如果有比较复杂的业务,我们需要写复杂的 SQL 语句,往往需 要拼接,而拼接 SQL ,稍微不注意,由于引号,空格等缺失可能都会导致错误。
那么怎么去解决这个问题呢?这就要使用 mybatis 动态SQL,通过 if, choose, when, otherwise, trim, where, set, foreach等标签,可组合成非常灵活的SQL语句,从而在提高 SQL 语句的准确性的同 时,也大大提高了开发人员的效率。
1. if
Dao层接口:
//需求1
List<Blog> queryBlogIf(Map map);
mapper.xml:
<!--需求1:
根据作者名字和博客名字来查询博客! 如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author} -->
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
JUnit测试:
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","Mybatis如此简单");
map.put("author","狂神说");
List<Blog> blogs = mapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title}, 但是如果title为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!
2. where
mapper.xml:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返 回的内容是以AND 或OR 开头的,则它会剔除掉。【这是我们使用的最多的案例】
3. Set (用于Update)
Dao层接口:
int updateBlog(Map map);
mapper.xml:
<!--注意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 id = #{id};
</update>
JUnit测试:
@Test
public void testUpdateBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","动态SQL");
map.put("author","秦疆");
map.put("id","9d6a763f5e1347cebda43e2a32687a77");
mapper.updateBlog(map);
session.close();
}
4. choose
有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句
Dao层接口:
List<Blog> queryBlogChoose(Map map);
mapper.xml:
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
JUnit测试:
@Test
public void testQueryBlogChoose(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title","Java如此简单");
map.put("author","狂神说");
map.put("views",9999);
List<Blog> blogs = mapper.queryBlogChoose(map);
System.out.println(blogs);
session.close();
}
5. Foreach
将数据库中前三个数据的id修改为1,2,3;
需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息
DAO层接口:
List<Blog> queryBlogForeach(Map map);
mapper.xml:
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection:指定输入对象中的集合属性
item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
JUnit测试:
@Test
public void testQueryBlogForeach(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
System.out.println(blogs);
session.close();
}
小结:其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生 的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它 的技巧
SQL片段
有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽 取出来,然后使用时直接调用。
提取SQL片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if> </sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上namespace -->
<include refid="if-title-author"></include>
<!-- 在这里还可以引用其他的 sql 片段 -->
</where>
</select>
Note:
- 最好基于 单表来定义 sql 片段,提高片段的可重用性
- 在 sql 片段中不要包括 where
IX. 缓存
1. 定义
什么是缓存 [ Cache ]?
存在内存中的临时数据。 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库 数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
为什么使用缓存?
减少和数据库的交互次数,减少系统开销,提高系统效率。
什么样的数据能使用缓存?
经常查询并且不经常改变的数据。
2. MyBatis中的缓存
MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的
提升查询效率。 MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
3. 一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 一级缓存的四种情况:
- 一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;
- 一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!
- 1) sqlSession不同 (每个sqlSession中的缓存相互独立)
- 2) sqlSession相同,查询条件不同(当前缓存中,不存在这个数据)
- 3) sqlSession相同,两次查询之间执行了增删改操作! (因为增删改操作可能会对当前数据产生影响)
- 4) sqlSession相同,手动清除一级缓存
4. 二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中;
- 结论
- 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
- 查出的数据都会被默认先放在一级缓存中
- 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中
5. 缓存原理
X. 坑
绑定接口错误:org.apache.ibatis.binding.BindingException
- 每一个mapper.xml都需要在mybatis-config.xml中配置
- 命名空间一定要正确(mapper.xml中的namespace标签和接口类名称对应)
方法名不对
- mapper.xml中每个SQL描述标记的id对应的方法名必须和接口中一致
返回类型不对
- mapper.xml中每个SQL描述标记的resultType/resultMap标签对应返回类型
Maven导出资源问题:java文件夹下除了.java文件没有被编译进target
- Maven配置文件需要在build中配置
事务:增删改后数据没有更新
- 增删改需要提交事务
XI. 总结
MyBatis是一款优秀的持久层框架。使用它让我们代替了繁琐的JDBC代码,其灵活性、简单易学等各种特性帮助我们在开发中省时省力。
MyBatis是SSM框架中的一部分,在大量的实战开发中被广泛使用。在理解好JavaWeb的前提下,使用MyBatis使得我们的开发更加高效,所以说MyBatis是程序员从理论到应用的重要一步。
Share this post
Twitter
Google+
Facebook
Reddit
LinkedIn
StumbleUpon
Pinterest
Email