目录
一、简介
MyBatis 是一款优秀的 持久层 框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
持久层:
- 持久化是将程序的数据在持久状态和瞬时状态转化的过程。比如内存是断电即失的,而数据库(JDBC)、io文件可以实现数据的持久化
- 持久层是为了完成持久化工作的代码块
为什么使用Mybatis:
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组件维护
- 提供xml标签,支持编写动态sql
现在在GitHub中下载:https://github.com/mybatis/mybatis-3。点击Releases下载最新版本
二、Hello Mybatis
- 创建数据库(Navicat)
- 新建项目
(1)新建一个普通的maven项目
(2)删除src目录
(3)导包:mysql驱动、mybatis、junit等
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
- 创建一个模块
(1)在上面的项目中新建一个module
核心配置文件
(2)编写mybatis的核心配置文件
在resources目录下,新建一个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="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
$ {driver}改成mysql驱动,$ {url}改成数据库的url,$ {username}和$ {password}改成MySQL的用户名和密码,例如:
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123321"/>
注意,xml中url的“&”要写成转义符“&”
工具类
(3)编写mybatis工具类
SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
package com.y.utils;
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.*;
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory ;
static {
//使用mybatis获取SqlSessionFactory对象
try {
String resource = "mybatis-config.xml";//读取resources目录下的文件时url可以直接为文件名
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
//用SqlSessionFactory获得 SqlSession 的实例
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
- 编写代码
- 实体类
package com.y.pojo;
import lombok.Data;
@Data
public class User {
private int id;
private String name;
private String pwd;
}
接口
package com.y.dao;
import com.y.pojo.User;
import java.util.List;
public interface UserDao {
List<User> getUserList();
}
Dao 接口的工作原理是什么?
通常一个 xml 映射文件(Mapper配置文件)对应实现一个 Dao 接口。接口的全限名,就是Mapper配置文件中的 namespace 的值;接口的方法名,就是映射文件中的 id 的值;接口方法内的参数,就是传递给 sql 的参数。
Mapper配置文件(接口实现类)
由原来的UserDaoImpl类转变为一个Mapper配置文件
(1)写Mapper配置文件: 在Dao接口的packet中新建一个xml,这里命名为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">
<!--namespace会绑定一个对应的DAO/Mapper接口-->
<mapper namespace="com.y.dao.UserDao">
<!--select查询语句
id对应接口中的一个方法名
resultType对应这个方法返回的数据类型
-->
<select id="getUserList" resultType="com.y.pojo.User">
select * from user
</select>
</mapper>
resultType注意:
- 如果接口中的这个方法返回的是集合,则只需要设为该集合的泛型类名即可
- 如果返回类型是引用类型,则SQL查询结果集将传给该类中与结果集字段名相同的属性。例如这个例子中,结果集中只有字段名为id、name和pwd的查询内容会分别传给User对应的属性
- 为了应对Java jdk中原始类型的命名重复,采取了特殊的命名风格,见Mybatis中常见的 Java 类型内建的类型别名
(2)在核心配置文件中注册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>
<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?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123321"/>
</dataSource>
</environment>
</environments>
<!--每一个Mapper.xml的路径都需要在核心配置文件中注册!-->
<mappers>
<mapper resource="com/y/dao/UserMapper.xml"/>
</mappers>
</configuration>
(3)由于Mapper配置文件是在java目录下的,因此需要在pom.xml中把配置文件包含进去:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
调用Mapper方法的测试程序
- 测试
在test中构建与main目录结构相同的packect, 并在其相应的包下新建测试程序:
import java.util.List;
public class UserDaoTest {
@Test
public void test(){
//获得sqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//方式一: getMapper(常用方式)
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
/*//方式二:
List<User> userList = sqlSession.selectList("com.y.dao.UserDao.getUserList");*/
for (User user:userList) {
System.out.println(user);
}
//关闭sqlSession
sqlSession.close();
}
}
测试执行结果:
三、CRUD
如何写Mapper配置文件(例如上面的UserMapper.xml)
1. namespace
namespace的值要和Dao/Mapper接口的名字一致:包名.文件名. 注意这并不是路径.
namespace不是必须的,但最好有,防止不同的接口出现相同的方法名(id)。
2. select
- id: 对应namespace中的方法名
- resultType: Sql语句执行的返回类型
- parameterType: 传入方法参数的类型
举例:
<select id="getUserById" resultType="com.y.pojo.User" parameterType="int">
select * from user where id = #{id}
</select>
#{}中的id是传参, 另一个id是数据库的字段名
#{} 和 ${} 的区别是什么?
· $ {}是 properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,详见二、2
· #{} 是 sql 的参数占位符,用于给 sql 设置参数值
· 可以用${}代替#{},但后者可以防止SQL注入的问题
//根据id查询用户
User getUserById(int id);
3. insert
<!--对象中的属性可以直接取出来-->
<insert id="addUser" parameterType="com.y.pojo.User">
insert into user (id, name, pwd) values (#{id}, #{name}, #{pwd})
</insert>
//插入一个用户
int addUser(User user);
注意:
- 执行增删改的方法后记得提交事务!
- 增删改没有resultType字段
- 只有一个传参且为基本类型参数时, 可以省略parameterType字段
4. update
<update id="updateUser" parameterType="com.y.pojo.User">
update user set name=#{name}, pwd=#{pwd} where id=#{id};
</update>
//修改用户
int updateUser(User user);
5. delete
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
</delete>
//删除用户
int deleteUser(int id);
重要技巧之当实体类或数据库表中的属性或字段过多时, 我们最好考虑使用Map
<insert id="addUser2" parameterType="Map">
insert into user (id, name, pwd) values (#{userid}, #{username}, #{password})
</insert>
//万能的Map
int addUser2(Map<String, Object> map);
@Test
public void testInsert2(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userid", 1);
map.put("username", "ycr");
map.put("password", "123321");
int res = userDao.addUser2(map);
//提交事务
sqlSession.commit();
sqlSession.close();
}
用Map传递参数时可以在sql中直接取key;
用对象传递参数时可以在sql中直接取属性名.
6. like(模糊查询)
//LIKE模糊查询
List<User> getUserLike(String name);
<select id="getUserLike" resultType="com.y.pojo.User" parameterType="String">
select * from user where name like #{name}
</select>
@Test
public void testSelectLike(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
List<User> userList = userDao.getUserLike("%zz%");
for (User user: userList) {
System.out.println(user);
}
sqlSession.close();
}
或者在UserMapper.xml中写成like “%”#{name}“%”, 并且在测试方法中调用方法时传入"zz".
四、 配置(configuration)
详见官网xml配置
核心配置文件(mybatis-config.xml)
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
注意: 标签顺序必须跟上面的一致
1. environments(环境配置)
MyBatis 可以配置成适应多种环境(environment, 环境变量). 不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
默认的transactionManager(事务管理器)是JDBC, dataSource(数据源)是POOLED.
2. properties(属性)
我们可以通过该字段实现引用配置文件。
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。
(1)在典型的 Java 属性文件中配置
resource/db.properties:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&;characterEncoding=utf-8&;useSSL=true
username=root
password="123321
核心配置文件:
<!--引入外部配置文件-->
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
(2)在 properties 元素的子元素中设置:
<!--引入外部配置文件-->
<properties>
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123321"/>
</properties>
也可以同时设置配置文件, 但如果配置文件中的字段名和properties中的重复了, 系统会优先读取配置文件中的
<!--引入外部配置文件-->
<properties resource="db.properties">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="111111"/>
</properties>
environments标签内容与前面的一样
3. typeAliases(类型别名)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
<!--给实体类取别名-->
<typeAliases>
<typeAlias alias="User" type="com.y.pojo.User"/>
</typeAliases>
当这样配置时,User 可以用在任何使用 com.y.pojo.User 的地方。
也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="com.y.pojo"/>
</typeAliases>-->
每一个在包 com.y.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 com.y.pojo.User 的别名为 user;(原名也可以)若有注解,则别名为其注解值。见下面的例子:
package com.y.pojo;
import lombok.Data;
import org.apache.ibatis.type.Alias;
@Alias("user")
@Data
public class User {
第一种和第三种方法可以任意设置别名,但第二种不行。
4. mappers(映射器)
我们需要告诉 MyBatis 到哪里去找到这些语句。
方法一(绑定配置文件):
<mappers>
<mapper resource="com/y/dao/UserMapper.xml"/>
</mappers>
方法二(绑定接口):
<mapper class="com.y.dao.UserMapper"/>
方法三(扫描包):
<package name="com.y.dao"/>
注意:
用class文件或扫描包这两种方法进行绑定注册时,接口和他的Mapper配置文件必须同名、同包。除非resources中的Mapper配置文件所在的目录结构与java目录下接口所在的包结构相同,这样它俩就会被打包到同一个classpath下,让Mybatis可以找到这两个文件:
五、作用域(Scope)和生命周期
一旦创建了 SqlSessionFactory,就不再需要SqlSessionFactoryBuilder了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。
六、 resultMap(结果集映射)
把pojo/User.java中的属性pwd改成password, 数据库中的pwd字段保持不变.
此时,
除了应用SQL中的AS语句外, 还可以通过在Mapper配置文件中的resultMap字段对实体类的属性和数据库的字段名建立映射关系.
Mapper配置文件:
<mapper namespace="com.y.dao.UserMapper">
<select id="getUserById" resultType="com.y.pojo.User" parameterType="int" resultMap="UserMap">
select * from user where id = #{id}
</select>
<resultMap id="UserMap" type="User">
<!--column为数据库中的字段名,property为实体类中的属性-->
<result column="pwd" property="password"/>
</resultMap>
</mapper>
七、日志
日志工厂
如果数据库操作出现了异常,需要排错,就需要日志。其他常见排错方式有控制台输出、debug。
1. STDOUT_LOGGING
在核心配置文件中增加settings标签:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
就可以看到控制台输出日志
2. LOG4J
- LOG4J是apache的一个开源项目,它可以控制日志信息输送的目的地,例如控制台、文件、GUI组件
- 我们可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,我们能更细致的控制日志的生成过程
- 通过一个配置文件来灵活的进行配置,而不需要修改应用的代码
使用:
- 导包
- 写一个.properties文件
内容如下
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4i.appender.console=org.apachelog4i.ConsoleAppender
log4j.appender.consoleTarget=System.out log4j.appender.consoleThreshold=DEBUG
log4j.appender.console.layout=org.apachelog4j.PatternLayout
log4j.appender.consolelayoutConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file=org.apachelog4j.RollingFileAppender
log4j.appender.fileFile=/log/kuang.log
log4j.appender.fileMaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apachelog4j.PatternLayout
1og4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sq1=DEBUG
log4j.logger.java.sq1.statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sq1.Preparedstatement=DEBUG
- 修改核心配置文件
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
- 在要使用日志的类中,
import org.apache.log4j.Logger
static Logger logger = Logger.getLogger(UserDaoTest.class);
logger的作用域设为这个类, UserDaoTest为这个类的名字
在该类的方法中可以增加日志输出:
logger.info("info日志");
logger.debug("debug日志");
logger.error("error日志");
八、分页
1. 用SQL的limit实现分页
UserMapper接口:
//分页
List<User> getUserByLimit(Map<String, Integer> map);
UserMapper配置文件:
<select id="getUserByLimit" resultType="User" parameterType="map" resultMap="UserMap">
select * from user limit #{startIndex}, #{pageSize}
</select>
测试程序:
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper userDao = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("startIndex", 0);
map.put("pageSize", 3);
List<User> userList = userDao.getUserByLimit(map);
for (User user:userList) {
System.out.println(user);
}
sqlSession.close();
}
注意学会使用Map。
2. Mybatis的RowBounds对象
public RowBounds(int offset, int limit)
<E> List<E> selectList(String var1, Object var2, RowBounds var3);
3. 分页插件PageHelper
九、使用注解开发
1. 面向接口编程
主要优点:解耦
三个面向的区别:
- 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性和方法
- 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
- 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构
2. 使用注解开发
本质:反射机制实现
核心:动态代理
- 在接口上实现注解
@Select("select * from user")
List<User> getUsers();
- 在核心配置文件中绑定接口
<!--绑定接口-->
<mappers>
<mapper class="com.y.dao.UserMapper"/>
</mappers>
使用注解分别实现增删改查:
- 如果要使用增删改,可以在工具类获取sqlSession的方法中统一设置自动提交事务
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
- 接口:
@Select("select * from user where id=#{ID}")
User getUserById(@Param("ID") int id);
@Insert("insert into user(id, name, pwd) values(#{id}, #{name}, #{password}) ")
int addUser(User user);
@Update("update user set name=#{name} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id=#{ID}")
int deleteUser(@Param("ID")int id);
@Param(“”)注解一般用在基本类型和String参数上,引用类型不需要
- 在核心配置文件中绑定接口
十、Mybatis底层及执行流程
debug:
Mapper中的sqlSession对象含有数据库配置,mapperInterface获取相应的接口(UserMapper),methodCache获取接口中要执行的方法
执行流程:
十、多对一和一对多处理
注意:保证SQL的可读性;属性名和字段名的对应问题
1. 多对一
要返回的pojo类中的一个属性是另外一个pojo类,且这两个类的数据要从不同的表中查询
现有两个表student和teacher,分别如下,
要求是在查询student时,根据student的tid字段同时查询对应的teacher表项并一起返回。
com.y.student:
@Data
public class Student {
private int id;
private String name;
private Teacher teacher;
}
com.y.teacher略。
方式一:按照查询嵌套处理(对应SQL的子查询/嵌套查询)
StudentMapper.xml:
<select id="getStudent" resultMap="Teacher_of_Student">
select * from student
</select>
<resultMap id="Teacher_of_Student" type="Student">
<!--如果返回的类属性是一个
引用对象:association
集合:collection
-->
<!--ofType用来指定映射到集合中泛型的引用类型-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
<!--<collection property=""/>-->
</resultMap>
<!--子查询(嵌套查询)SQL-->
<!--#{}内传入的值是resultMap-association中column指示的参数,因此里面可以任意填-->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id = #{id}
</select>
方式二:按照结果嵌套处理(对应SQL的多表查询)
StudentMapper.xml:
<!--多表查询SQL-->
<select id="getStudent2" resultMap="Teacher_of_Student_2">
select s.id as id, s.name as name, t.id as tid, t.name as tname
from student as s, teacher as t
where s.tid=t.id
</select>
<resultMap id="Teacher_of_Student_2" type="Student">
<!--result字段不能省略,否则返回结果为空-->
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
测试结果:
2. 一对多
要返回的pojo类中的一个属性是另外一个pojo类对象的集合,且这两个类的数据要从不同的表中查询
保持teacher表不变,student表增加表项:
保持Student类不变,给Teacher类增加一个泛型为Student的集合类属性:
@Data
public class Teacher {
private int id;
private String name;
private List<Student> students;
}
方式一
TeacherMapper.xml:
<select id="getTeacher2" resultMap="Student_of_Teacher2">
select * from teacher
</select>
<resultMap id="Student_of_Teacher2" type="Teacher">
<!--id字段被collection占用,因此要对它进行映射-->
<result property="id" column="id"/>
<!--ofType用来指定映射到集合中泛型的引用类型-->
<collection property="students" ofType="Student" column="id" select="getStudents"/>
</resultMap>
<select id="getStudents" resultType="Student">
select * from student where tid=#{id}
</select>
方式二
TeacherMapper.xml:
<select id="getTeacher" resultMap="Student_of_Teacher">
select t.id id, t.name name, s.id sid, s.name sname
from teacher t, student s
where t.id = s.tid
</select>
<resultMap id="Student_of_Teacher" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="id"/>
</collection>
</resultMap>
测试结果:
十一、动态SQL
动态SQL就是根据不同的条件生成不同的SQL语句,避免手动拼接SQL。
先写基本的程序和数据:
- 数据库
- 实体类
package com.y.pojo;
import lombok.Data;
import java.util.Date;
import java.util.function.DoubleToIntFunction;
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
- 数据库中的字段名映射到Java类的驼峰命名
在核心配置文件中:
<settings>
<!--从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn(驼峰命名法)。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- 生成UUID随机数作为id字段的值
重要技巧之用SuppressWarnings注解抑制警告
package com.y.utils;
import org.junit.Test;
import java.util.UUID;
@SuppressWarnings("all")//抑制所有类型的警告
public class IDutils {
public static String getId(){
/*.replaceAll将字符串中的 - 全部替换为空字符*/
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
- 插入数据
Mapper接口、配置文件和执行程序省略,执行程序中id字段值获取方式如下
Blog blog = new Blog();
blog.setId(IDutils.getId());
if
根据测试程序传入的参数拼接查询条件,如果test程序中的map中没有title和author这两个键或这两个键的值,就执行select * from blog where 1=1;如果满足if标签中test的语句为true,就拼接SQL语句并执行。
Mapper配置文件:
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from blog where 1=1
<if test="title != null">
and title =#{title}
</if>
<if test="author != null">
and author =#{author}
</if>
</select>
test程序:
@Test
public void testQueryBlogIF(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
HashMap<Object, Object> map = new HashMap<>();
map.put("title", "Mybatis");
List<Blog> blogs = sqlSession.getMapper(BlogMapper.class).queryBlogIF(map);
for (Blog blog:blogs) {
System.out.println(blog);
}
sqlSession.close();
}
结果:
。
Dao 接口里的方法,参数不同时,能重载吗?
Dao 接口里的方法可以重载,但是xml映射文件里面的id 不允许重复,即重载的多个方法必须用同一个id,然后用动态SQL的if
实现方法的重载。
choose、when、otherwise
choose-when-otherwise相当于Java里的switch-case:顺序判断when的test语句,先满足条件的进行拼接;如果都不满足,直接拼接otherwise的SQL。
<select id="queryBlogWHERE" parameterType="map" resultType="Blog">
select * from blog where 1=1
<choose>
<when test="title != null">
AND title = #{title}
</when>
<when test="author != null">
AND author = #{author}
</when>
<otherwise>
AND id=#{id}
</otherwise>
</choose>
</select>
trim(where、set)
将上一个的“1=1”去掉,就需要用where元素:
<select id="queryBlogWHERE" parameterType="map" resultType="Blog">
select * from blog
<where>
<if test="title != null">
AND title = #{title}
</if>
<if test="author != null">
AND author=#{author}
</if>
</where>
</select>
注意:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句,而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除;如果where元素里的条件都不满足,则相当于没有where。
update需要用set元素:
<update id="updateBlogSET" parameterType="map">
update blog
<set>
/*注意set语句中的每一个等式中间需要用逗号隔开*/
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id=#{id}
</update>
注意:set元素会自动删除多余的逗号
SQL片段
上面的几个SQL映射元素中有一部分是重复使用的,我们可以:用sql元素抽取公共的部分,然后在需要使用的地方用include标签引用:
注意,被引用的标签依然可以定义在任何地方,可以在引用标签的前面或者后面,MyBatis 都可以正确识别。
<sql id="if-title-author">
<if test="title != null">
and title =#{title}
</if>
<if test="author != null">
and author =#{author}
</if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from blog where 1=1
<include refid="if-title-author"></include>
</select>
foreach
<select id="queryBlogFOREACH" resultType="Blog" parameterType="map">
SELECT * FROM Blog
<where>
<foreach item="id" collection="ids"
open="(" separator="or" close=")" nullable="true">
id=#{id}
</foreach>
</where>
</select>
这段动态sql相当于SELECT * FROM Blog WHERE (id=#{id} or id=#{id} or ......)
其中item的值必须和#{}中间的一样,是集合中的每一项,collection是集合名。
测试程序:
@Test
public void testQueryBlogFOREACH(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
HashMap<String, List> map = new HashMap<>();
LinkedList<String> ids = new LinkedList<>();
ids.add("ec64aedf13ab47e5beb0e534efaee0fe");
ids.add("ed74e7f370de4b19b7ad3c5b0bbffd61");
map.put("ids", ids);
List<Blog> blogs = sqlSession.getMapper(BlogMapper.class).queryBlogFOREACH(map);
for (Blog blog:blogs) {
System.out.println(blog);
}
sqlSession.close();
}
最终执行的SQL语句是SELECT * FROM Blog WHERE (id='ec64aedf13ab47e5beb0e534efaee0fe' or id='ed74e7f370de4b19b7ad3c5b0bbffd61')
十二、缓存
1. 简介
SQL查询连接数据库有一个耗资源的缺点,解决办法是把一次查询的结果暂时存储,再次查询相同的内容是就直接从这个缓存里查找。
使用缓存可以减少和数据库的交互次数,减少系统开销,提高系统效率。
什么样的数据库能使用缓存?查询多且改变少的数据库。
Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
默认情况下只有一级缓存开启(sqlSession级别的缓存,也称本地缓存)
二级缓存需要手动开启和配置,它是基于namespace级别的缓存
为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过Cache来自定义二级缓存
idea技巧之快速查找文件
连按两下Shift,就会跳出搜索框,输入想查找的文件,例如jdk、导入的jar包等等当中的.class文件
2. 一级缓存
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
System.out.println("=============");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession.close();
}
结果:
说明,重复查询的结果集内存地址相同!
两次查询中只有第一次进入了数据库查询,第二次是在内存中查询。
缓存失效的情况:
- 不同的查询
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
System.out.println("=============");
User user2 = mapper.queryUserByName("ycr");
System.out.println(user2);
System.out.println(user1==user2);
sqlSession.close();
}
结果:
-
用增删改改变了数据库。因为所有 insert、update 和 delete 语句会刷新缓存。
-
查询不同的Mapper.xml
-
手动清理缓存
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.queryUserById(1);
System.out.println(user1);
System.out.println("=============");
sqlSession.clearCache();//手动清理缓存
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession.close();
}
结果:false
进行了两次数据库查询。
因此,一级缓存默认开启,只在一次sqlSession中有效,也就是连接到关闭连接这个区间内有效。
底层原理:一级缓存维护的是一个Map
2. 二级缓存
- 二级缓存也叫全局缓存。一级缓存的作用域太低,因此诞生了二级缓存
- 一个命名空间对应一个二级缓存
- 工作机制:
- 如果一个会话关闭了,这个会话对应的一级缓存就没了,但是我们可以让一级缓存中的数据保存到二级缓存中;
- 新的会话查询信息可以从二级缓存中获取;
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
- 需要注意的是,只有当会话提交或者关闭的时候,才会存到二级缓存中
步骤:
- 开启全局缓存
方式一(不常用):通过核心配置文件的settings
方式二:通过映射文件的<cache/>
标签。可以自定义缓存参数,详见官方文档。
- 测试
注意:序列化异常的解决办法是开启cache的只读:
<cache readOnly="true"/>
@Test
public void test(){
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
User user1 = sqlSession1.getMapper(UserMapper.class).queryUserById(1);
System.out.println(user1);
sqlSession1.close();
System.out.println("=============");
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
User user2 = sqlSession2.getMapper(UserMapper.class).queryUserById(1);
System.out.println(user2);
System.out.println(user1==user2);
sqlSession2.close();
}
结果:
3. Mybatis缓存原理
查询数据的顺序:二级缓存—>一级缓存—>数据库
4. 自定义缓存
可以通过实现Cache接口的方式自定义一种缓存, 详见官方文档. Ehcache是一种已实现的缓存.