Mybatis学习笔记
Mybatis
官网:(Mybatis)[https://mybatis.org/mybatis-3/index.html]
I. 简介
- MyBatis 是一款持久层框架
- 它支持自定义 SQL、存储过程以及高级映射
- 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
II. 一个简单的Mybatis程序
- 创建一个maven工程。
- 导入maven依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.nl</groupId>
<artifactId>mybatis</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>mybatis_01_helloword</module>
</modules>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- 单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- Lombok项目是一个Java库,可以不用写getter,setter方法,通过注解即可完成(@Getter等)。-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
<!-- 解决资源过滤问题-->
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
说明:<build>
节点是为了在target对应目录下生成对应的配置文件。否则会因为初始化异常而报错。
2.1 删除src目录。
2.2 新建一个Module,选择maven工程。填写好项目和模块名,Finish。
2.3 写入maven依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>mybatis</artifactId>
<groupId>com.nl</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybatis_01_helloword</artifactId>
</project>
说明:每次都要在pom.xml写入相同的依赖,我们可以将相同的内容写在一个pom.xml,其他工程可通过<parent>
节点进行引用。
- 从xml中构建SqlSessionFactory
-
每个基于mybatis的应用的核心都是一个SqlSessionFactory的实例。
-
SqlSessionFactory的实例可以通过SqlSessionFactoryBuilder获取。
-
可以从xml配置文件(或预先配置的Connfiguration实例)构建SqlSessionnFactory。
我们可以将获取SqlSessionnFactory的代码块放入到工具类中:
package com.nl.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.IOException; import java.io.InputStream; public class MybatisUtils { private static SqlSessionFactory sqlSessionFactory; static { try { String resource= "mybatis-config.xml"; //获取sqlSessionFactory对象 InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return sqlSessionFactory.openSession(); } }
说明:resource为mybatis的核心配置文件。SqlSession对象包含了所有对数据库的操作(增删改查,以及事务的提交回滚等操作)。
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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <!-- type有三种取值:UNPOOLED POOLED JNDI--> <!-- POOLED是利用了数据库连接池的原理--> <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=UTF-8"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments> <!-- 对应于实现UseDao接口的UserMapper.xml文件 --> <mappers> <mapper resource="com/nl/dao/UserMapper.xml"/> </mappers> </configuration>
注意:若mysql驱动为8.0版本,则驱动的value最好是com.mysql.cj.jdbc.Driver
- 编写实体类
package com.nl.pojo;
import lombok.*;
@Setter
@Getter
@ToString
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private String password;
}
-
编写Dao层
5.1 UseDao接口
package com.nl.dao; import com.nl.pojo.User; import java.util.List; public interface UserDao { List<User> getUsers(); }
5.2 UseDao的实现
以前我们会放在一个类中,并实现相应的方法。现在我们将实现写在了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.nl.dao.UserDao"> <!-- select查询语句--> <!-- id为接口对应的方法名--> <!-- resultType为返回值List<User>的泛型的全限定类名--> <select id="getUsers" resultType="com.nl.pojo.User"> select id,username,password from mybatis.user </select> </mapper>
- 测试
package com.nl.dao; import com.nl.pojo.User; import com.nl.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; public class UserDaoTest { @Test public void userMapperTest(){ //获取sqlSession对象 SqlSession sqlSession = MybatisUtils.getSqlSession(); try{ //执行sql语句 //方式一:getMapper() UserDao mapper = sqlSession.getMapper(UserDao.class); List<User> users = mapper.getUsers(); users.forEach(System.out::println); //方式二 // users = sqlSession.selectList("com.nl.dao.UserDao.getUserList"); }catch (Exception e){ e.printStackTrace(); }finally { sqlSession.close(); } } }
III. CRUD
Mapper接口
package com.nl.dao;
import com.nl.pojo.User;
import java.util.List;
public interface UserMapper {
//查询所有用户
List<User> getUsers();
//根据id查询用户
User getUserById(int id);
//插入用户
int addUser(User user);
//修改用户
int updateUser(User user);
//删除用户
int deleteUser(int id);
}
1. select
查询语句
- id:为namespace对应的方法名。
- resultType:Sql执行后的返回值。
- parameterType:参数类型。
<select id="getUserById" resultType="com.nl.pojo.User" parameterType="int">
select id,username,password from user where id=#{id}
</select>
2. insert
<insert id="addUser" parameterType="com.nl.pojo.User">
insert into user(id,username,password) value(#{id},#{username},#{password})
</insert>
3. update
<update id="updateUser" parameterType="com.nl.pojo.User">
update user set username=#{username},password=#{password} where id=#{id};
</update>
4. delete
<delete id="deleteUser" parameterType="int">
delete from user where id=#{id}
</delete>
以update为例进行测试
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int res = mapper.updateUser(new User(0007, "abc", "abc0007"));
if (res > 0){
//提交事务
sqlSession.commit();
}
sqlSession.close();
}
5. 关于map
若实体类的属性,数据库的字段过多时,可使用map。
//接口
//方式二:通过map插入用户
int addUser1(Map<String,Object> map);
//实现
<insert id="addUser1" parameterType="map">
insert into user(id,username,password) value(#{id},#{name},#{pwd})
</insert>
//测试
@Test
public void addUser1Test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("id",5);
map.put("name","other");
map.put("pwd","other0005");
int res = mapper.addUser1(map);
if(res > 0){
sqlSession.commit();
}
sqlSession.close();
}
通过map来封装实体类的属性。
6. 模糊查询
List<User> getUsersLike(String nameStr);
<select id="getUsersLike" resultType="com.nl.pojo.User">
select id,username,password from user where username like #{nameStr}
</select>
-
执行java代码时,传递通配符
@Test public void getUsersLike(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> users = mapper.getUsersLike("%o%"); users.forEach(System.out::println); sqlSession.close(); }
-
在sql中拼接通配符
<select id="getUsersLike" resultType="com.nl.pojo.User"> select id,username,password from user where username like "%"#{nameStr}"%" </select>
注:若在一个select节点中写两条语句,即使一条被注释掉,也会报如下异常
Caused by: java.sql.SQLException: Parameter index out of range (2 > number of parameters, which is 1).
IV. 配置解析
1. 核心配置文件
1.1 mybatis-config.xml的层次结构
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
配置的顺序
1.2 environments(环境配置)
每个SqlSessionFactory实例只能选择一种环境。
1.2.1 事务管理器(transactionManager)
mybatis中有两种类型的事务管理器。JDBC
和MANAGED
- JDBC 使用了JDBC的提交和回滚。
- MANAGED 让容器管理事务的整个生命周期。使用spring+mybatis则不必配置事务管理。
1.2.2 数据源(dataSource)
有三种数据源类型:UNPOOLED
POOLED
JNDI
-
UNPOOLED
每次请求都会打开和关闭连接。 -
POOLED
利用“池”的概念组织JDBC
的连接。 -
JIDI
是为了数据源能在EJB这类WEB服务器中使用。
1.3 properties(属性)
我们可以通过外部文件替换mybatis文件中的属性。比如,我们可以将数据库的driver,url,user,password写在外部文件中,在mybatis文件中再对其进行引用。
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?
useSSL=true&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
mybatis-config.xml
<configuration>
<!-- 引入外部文件db.properties-->
<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>
<mappers>
<mapper resource="com/nl/dao/UserMapper.xml"/>
</mappers>
</configuration>
也可在properties
标签中加入新的属性,若外部文件与properties
有相同属性时,外部文件属性优先级更高。
1.4 typeAliases(类型别名)
可以通过别名的方式来减少全限定类名的书写。
-
typeAlias
节点
<configuration>
<typeAliases>
<typeAlias type="com.nl.pojo.User" alias="user"/>
</typeAliases>
<!--...-->
</configuration>
在mapper.xml中使用
<select id="getUsers" resultType="user">...</select>
-
package
节点
<typeAliases>
<package name="com.nl.pojo"/>
</typeAliases>
此时,该包下的所有java bean的别名为首字母小写的类型。如,该包下的User类,别名即为user。
- @Alias(" ")注解
@Alias("u")
public class User{}
-
常用数据类型的别名
-
基本类型 别名在数据类型前加
_
即可别名 类型 _byte byte _long long _short short _int int _integer int _double double _float float _boolean boolean -
引用类型和包装类 别名为类名全小写
别名 类型 string String int Integer integer Integer double Double date Date decimal BigDecimal bigdecimal BigDecimal object Object map Map hashmap HashMap arraylist ArrayList iterator Iterator
-
1.5 settings(设置)
名称 | 描述 | 取值 |
---|---|---|
cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true|false |
lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 |
true|false |
mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true|false |
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING |
配置文件:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
</settings>
1.6 mappers(映射器)
方式一:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="com/nl/dao/UserMapper.xml"/>
</mappers>
方式二:
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="com.nl.dao.UserMapper"/>
</mappers>
说明:
-
接口和mapper配置文件必须同名。
-
接口和mapper配置文件必须在同一个包下。
方式三:
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="com.nl.dao"/>
</mappers>
说明:
与方式二的一样,必须同名,且在同一个包下。
V. 生命周期与作用域
SqlSessionFactoryBuilder
- 创建SqlSessionFactory后,就不再需要它。
- 局部变量
SqlSessionFactory
- SqlSessionFactory被创建后,就应该一直存在在应用的运行期间。
- 单例模式或静态单例模式。
SqlSession
- 每个线程都应该有它自己的 SqlSession 实例。
- SqlSession 的实例不是线程安全的,因此是不能被共享的。
- 请求或方法。
- 打开一个SqlSession,返回响应后,就关闭它。
VI. resultMap
解决属性名与字段名不一致的问题。
属性:
package com.nl.pojo;
import lombok.*;
@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String username;
private String pwd;
}
user表的字段:
id username pwd
由id查询user:
<select id="getUserById" resultType="user" parameterType="int">
select id,username,password from user where id=#{id}
</select>
测试:
@Test
public void getUsers(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user= mapper.getUserById(5);
System.out.println(user);
sqlSession.close();
//结果:User(id=5, username=other, pwd=null)
}
我们发现pwd属性为null,而数据库中password对应字段的值为other0005。这就是类的属性名和表中字段名不同造成的。
为此,我们可以使用resultMap
<mapper namespace="com.nl.dao.UserMapper">
<!-- 结果集映射-->
<resultMap id="userMap" type="user">
<!-- property对应实体类的属性,column对应数据表的字段。-->
<!-- <result property="id" column="id"/>-->
<!-- <result property="username" column="username"/>-->
<result property="pwd" column="password"/>
</resultMap>
<select id="getUserById" resultType="user" parameterType="int" resultMap="userMap">
select id,username,password from user where id=#{id}
</select>
</mapper>
VII. 日志
Mybatis中的日志类型:SLF4J 、 LOG4J 、 LOG4J2 、 JDK_LOGGING 、 COMMONS_LOGGING 、 STDOUT_LOGGING 、 NO_LOGGING
- STDOUT_LOGGING(标准日志)
配置:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
日志:
Opening JDBC Connection
Created connection 271800170.
Setting autocommit to false on JDBC Connection [aaa@qq.com]
==> Preparing: select id,username,password from user where id=?
==> Parameters: 2(Integer)
<== Columns: id, username, password
<== Row: 2, 李四, lisi0002
<== Total: 1
User(id=2, username=李四, pwd=lisi0002)
Resetting autocommit to true on JDBC Connection [aaa@qq.com]
Closing JDBC Connection [aaa@qq.com]
Returned connection 271800170 to pool.
-
LOG4J
- 是Apache的一个开源项目。通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等。
- 可以控制每一条日志的输出格式。
- 定义每一条日志信息的级别。
- 可以通过一个配置文件来进行配置,而不需要修改代码。
使用:
- 导入maven依赖
<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/nl.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
- 配置
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
Log4j的使用:
import org.apache.log4j.Logger; import org.junit.Test; public class Log4jTest{ private static Logger logger = Logger.getLogger(UserMapperTest.class); @Test public void log4jTest(){ logger.info("info..."); logger.debug("debug..."); logger.error("error..."); } }
日志:
[com.nl.dao.UserMapperTest]-info... [com.nl.dao.UserMapperTest]-debug... [com.nl.dao.UserMapperTest]-error...
VIII. 分页
8.1 limit分页
# 查询表中的前n条数据
select * from user limit n
-- 从第(n+1)行开始,检索m条记录
select * from user limit n,m
8.2 Mybatis实现分页
mapper接口
public interface UserMapper {
//分页查询
List<User> getUsersByLimit(Map<String,Integer> map);
}
mapper.xml
<mapper namespace="com.nl.dao05.UserMapper">
<select id="getUsersByLimit" parameterType="map" resultType="user">
select id,username,password from user limit #{beginIndex},#{pageSize}
</select>
</mapper>
测试:
package com.nl.dao05;
import com.nl.pojo.User;
import com.nl.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapperTest {
private static Logger logger = Logger.getLogger(UserMapperTest.class);
@Test
public void userMapperTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("beginIndex",1);
map.put("pageSize",3);
List<User> users = mapper.getUsersByLimit(map);
users.forEach(System.out::println);
sqlSession.close();
}
}
8.3 RowBounds
接口:
List<User> getUsersByRowBounds();
mapper:
<select id="getUsersByRowBounds" resultType="user">
select id,username,password from user;
</select>
测试:
@Test
public void getUsersByRowBounds(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
//通过RowBounds实现
RowBounds rowBounds = new RowBounds(1,2);
//通过java代码实现
List<Object> objects =
sqlSession.selectList("com.nl.dao05.UserMapper.getUsersByRowBounds", null, rowBounds);
objects.forEach(System.out::println);
sqlSession.close();
}
8.4 分页插件
Mybatis Page Helper (分页插件)[https://pagehelper.github.io/]
IX. 使用注解开发
9.1 注解实现
接口与实现:
@Select("select id,username,password from user")
List<User> getUsers();
在映射器中绑定接口:
<mappers>
<mapper class="com.nl.dao06.UserMapper"/>
</mappers>
测试
说明:底层为动态代理
9.2 CRUD
设置自动提交事务
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
接口与实现
package com.nl.dao06;
import com.nl.poji06.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
/**
* 根据id查询
* 当有多个参数时,在参数前需加入@Param注解
* @param id 注解中的名字需与sql中的保持一致
* @return
*/
@Select("select id,username,password from user where id=#{uid}")
User getUserById(@Param("uid") int id);
@Insert("insert into user(id,username,password) value(#{id},#{username},
#{password})")
int addUser(User user);
@Update("update user set username=#{username},password=#{password} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id=#{uid}")
int deleteUser(@Param("uid") int id);
}
关于@Param注解
- 基本数据类型与String类型,需要加该注解。
- @Param(“xxx”)中的属性名须与sql语句中的保持一致。
X. mybatis执行流程
XI. Lombok
关于Lombok
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
使用
-
下载插件
File ==> Settings ==> Plugins 搜索Lombok并下载。
-
导入maven依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
- 使用注解
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
@Data
包含了无参构造,setter,getter,toString,hashcode,equals
XII. 多对一处理
多对一:
理解模型:一个老师教许多学生。
对学生来说 就是多对一,关联关系。
对老师来说 就是一对多,集合关系。
环境搭建
- 建表
teacher表
字段:id name 其中id为主键。
stu表
字段:id name tid 其中id为主键,tid为外键(即teacher表的id)
-
实体类
Stu类
package com.nl.pojo07;
import lombok.Data;
@Data
public class Stu {
private Integer id;
private String name;
// 学生要关联一个老师
private Teacher teacher;
}
Teacher类
package com.nl.pojo07;
import lombok.Data;
@Data
public class Teacher {
private Integer id;
private String name;
}
- 编写接口(StuMapper)
//查询所有学生,及对应的老师
List<Stu> getStus();
- 绑定接口
<mappers>
<mapper class="com.nl.dao07.StuMapper"/>
</mappers>
- 实现接口
一般配置文件会放置在maven项目中的resources目录下。我们最好保证接口和实现在一个目录下。若StuMapper接口在java目录下的com.nl.dao包下,我们在resources目录下也会新建xom.nl.dao包,然后新建一个StuMapper.xml文件。
按照查询嵌套处理
<mapper namespace="com.nl.dao07.StuMapper">
<!-- 思路:-->
<!-- 查出所有学生。-->
<!-- 再由学生的tid查询对应的老师。-->
<select id="getStus" resultMap="StuTeacher">
select id,name,tid from stu
</select>
<resultMap id="StuTeacher" type="stu">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!-- 复杂属性,需单独处理 对象:association 集合:collection-->
<association property="teacher" column="tid" javaType="com.nl.pojo07.Teacher"
select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select id,name from teacher where id=#{id}
</select>
</mapper>
按照结果嵌套处理
<select id="getStus1" resultMap="StuTeacher2">
select stu.id,stu.name,t.name t_name from stu join teacher t on stu.tid = t.id
</select>
<resultMap id="StuTeacher2" type="stu">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" javaType="com.nl.pojo07.Teacher">
<result property="name" column="t_name"/>
</association>
</resultMap>
Sql多对一查询
- 子查询
- 联表查询
XIII. 一对多
接口
List<Teacher> getTeachers(@Param("tid") int id);
实体类
@Data
public class Teacher {
private int id;
private String name;
//一个老师有多个学生
private List<Stu> stus;
}
按照结果嵌套处理
<select id="getTeachers" resultMap="TeacherStu">
select stu.id s_id,stu.name s_name,teacher.id t_id,teacher.name t_name from stu join teacher on stu.tid = teacher.id where stu.tid=#{tid};
</select>
<resultMap id="TeacherStu" type="com.nl.pojo08.Teacher" >
<result property="id" column="t_id"/>
<result property="name" column="t_name"/>
<!-- javaType指定属性的类型。-->
<!-- 集合中的泛型,使用ofType属性-->
<collection property="stus" ofType="com.nl.pojo08.Stu">
<result property="id" column="s_id"/>
<result property="name" column="s_name"/>
<result property="tid" column="t_id"/>
</collection>
</resultMap>
按照查询嵌套处理
<select id="getTeachers1" resultMap="TeacherStu1">
select id,name from teacher where id=#{tid};
</select>
<resultMap id="TeacherStu1" type="teacher08">
<!-- id,name属性可以省略-->
<collection property="stus" javaType="java.util.ArrayList" ofType="stu08"
select="getStuByTeacherId" column="id"/>
</resultMap>
<select id="getStuByTeacherId" resultType="stu08">
select id,name,tid from stu where tid=#{tid}
</select>
关于javaType和ofType属性
- javaType 用于指定实体类属性。
- ofType 用于指定映射到集合中的pojo类型。
XIV. 动态SQL
什么是动态SQL
根据条件生成相应的SQL语句。
类似于jstl。
元素
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
环境搭建
- 建表
create table blog(
id varchar(50) not null comment '博客id',
title varchar(100) not null comment '博客标题',
author varchar(30) not null comment '博客作者',
create_time datetime not null comment '创建时间',
views int(30) not null comment '浏览量'
)
- 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
说明:@Data
注解中是包含有无参构造的,但如果只写@Data
和有参构造,是会覆盖掉无参构造的。
- 工具类
- mybatis工具类(参见之前的文档)
- IDUtils工具类
public class IDUtils {
//用于获取随机id
public static String getRandomId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
- 接口
//增加博客
int addBlog(Blog blog);
- 实现接口
<mapper namespace="com.nl.dao09.BlogMapper">
<insert id="addBlog" parameterType="blog">
insert into blog(id, title, author, crreate_time, views)
values(#{id},#{title},#{author},#{createTime},#{views})
</insert>
</mapper>
- 绑定接口
<mappers>
<mapper class="com.nl.dao09.BlogMapper"/>
</mappers>
- 向表中添加数据(测试)
@Test
public void addBlogTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog1 = new Blog(IDUtils.getRandomId(),"关于mybatis","nl",new Date(),888);
mapper.addBlog(blog1);
Blog blog2 = new Blog(IDUtils.getRandomId(),"spring的正确打开方式","sunaph",
new Date(),999);
mapper.addBlog(blog2);
Blog blog3 = new Blog(IDUtils.getRandomId(),"微服务架构","jessica",new Date(),1111);
mapper.addBlog(blog3);
sqlSession.close();
}
14.1 if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。
<select id="queryBlogIf" parameterType="map" resultType="blog">
select id, title, author, create_time, views from blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
14.2 choose、when、otherwise
它有点像 Java 中的 switch 语句。
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select id, title, author, create_time, views from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
author = #{author}
</when>
<otherwise>
views = #{views}
</otherwise>
</choose>
</where>
</select>
说明:
- 只要其中有一个满足,就会结束。
- 如果都不满足,会执行otherwise。
- 如果都满足,只会执行第一个when。
14.3 trim、where、set
The where element knows to only insert “WHERE” if there is any content returned by the containing tags. Furthermore, if that content begins with “AND” or “OR”, it knows to strip it off.
where后的子元素有返回值时,才会插入where。而且,如果子句开头是AND或OR,则会去掉。
<select id="queryBlogIf" parameterType="map" resultType="blog">
select id, title, author, create_time, views from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
在第一个if元素中加入and或者or,是会自动去掉的。
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updateBlogSet" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
如果第一个if不满足条件,则会去电语句中的,
符号。
trim用于定制其它元素
<trim prefix="SET" suffixOverrides=",">
...
</trim>
14.4 SQL片段
定义SQL片段
<sql id="double_if">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
使用include元素进行引用
<select id="queryBlogIf" parameterType="map" resultType="blog">
select id, title, author, create_time, views from blog
<where>
<include refid="double_if"/>
</where>
</select>
说明:若有多个地方都是用相同的SQL片段,我们可以将其放入sql元素中,在需要的提供进行引用。类似于java中的方法复用。
注意
- 最好在单表中定义SQL片段。
- 片段中不要包含where元素。
14.5 foreach
常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
<!-- collection要遍历的集合-->
<!-- item为集合中的每一项-->
<!-- open开始标识符-->
<!-- close结束标识符-->
<!-- separator分隔符-->
<!-- select * from blog where 1==1 (views=888 or views=999)-->
<select id="queryBlogForeach" resultType="blog" parameterType="map">
select id, title, author, create_time, views from blog
<where>
<foreach collection="view" item="views" open="and (" close=")" separator="or">
views = #{views}
</foreach>
</where>
</select>
测试
@Test
public void queryBlogForeachTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map map = new HashMap();
List<Integer> view = new ArrayList<>();
view.add(888);
view.add(999);
map.put("view",view);
// map.put();
mapper.queryBlogForeach(map);
sqlSession.close();
}
XV. 缓存
15.1 缓存Cache
概念
- 存在内存中的数据。
- 将用户经常查的数据放在缓存中,用户再次查询时,从缓存中读取数据。从而提升效率,解决系统并发的性能问题。
目的
- 减少与数据库的交互次数,减少系统开销,提高效率。
使用场景
- 经常查询且不经常修改的数据。
15.2 Mybatis缓存
- Mybatis中有两种缓存:一级缓存和二级缓存。
- 默认开启一级缓存。(SqlSession级别的缓存,也称为本地缓存)
- 二级缓存需要手动开启与配置,是基于namespace级别的缓存。
- 另外,Mybatis中定义了Cache接口,用于自定义二级缓存。
缓存清除策略
-
LRU
– 最近最少使用:移除最长时间不被使用的对象。 -
FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。 -
SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。 -
WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认LRU
缓存失效
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
15.3 一级缓存
- 一级缓存也称本地缓存。
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 之后需要获取相同的数据,直接从缓存中取。
测试环境搭建
-
实体类
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String username; private String password; }
-
接口
public interface UserMapper { User getUserById(@Param("id") int id); }
-
接口实现
<mapper namespace="com.nl.dao10.UserMapper"> <select id="getUserById" resultType="user10"> select id,username,password from user <where> id = #{id} </where> </select> </mapper>
-
绑定接口
-
测试
@Test public void getUserByIdTest(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user1 = mapper.getUserById(10); System.out.println("========"); User user2 = mapper.getUserById(10); System.out.println(user1 == user2);//true sqlSession.close(); }
日志信息如下:
... Created connection 1062186835. ==> Preparing: select id,username,password from user WHERE id = ? ==> Parameters: 10(Integer) <== Columns: id, username, password <== Row: 10, 王四, wangsi0010 <== Total: 1 ======== true ...
说明:我们从日志中可以看出,当再次查询时,并没有重新创建链接。而且我们发现两次的返回值是同一个对象。**这也表明mybatis是默认开启本地缓存的。**而缓存的作用域是从
getSqlSession()
开始,sqlSession.close()
结束。但如果是增删改操作,则每次操作都会创建新的连接。
15.4 二级缓存
- 也称全局缓存
- 基于namespace级别的缓存,一个命名空间,对应一个缓存
- 机制
- 一个会话查询一条数据,这条数据就会被放在当前会话的一级缓存中。
- 会话关闭,缓存就没有了。如果需要,一级缓存的数据就会被保存到二级缓存中。
- 新的会话就可以从二级缓存中获取数据。
- 不同的mapper查出的数据就会放在自己对应的缓存中(map)。
代码
-
显式地在
mybatis-config.xml
文件中开启二级缓存。<settings> <setting name="logImpl" value="STDOUT_LOGGING"/> <setting name="cacheEnabled" value="true"/> </settings>
-
在mapper中使用
自定义缓存
<mapper namespace="com.nl.dao10.UserMapper"> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> </mapper>
默认缓存
<mapper namespace="com.nl.dao10.UserMapper"> <cache/> </mapper>
-
测试
@Test
public void globalCacheTest(){
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user1 = mapper1.getUserById(19);
sqlSession1.close();
System.out.println("=====");
User user2 = mapper2.getUserById(19);
System.out.println(user1 == user2);
sqlSession2.close();
}
日志信息:
Created connection 1551945522.
==> Preparing: select id,username,password from user WHERE id = ?
==> Parameters: 19(Integer)
<== Columns: id, username, password
<== Row: 19, 张大, zhangda0019
<== Total: 1
Closing JDBC Connection [aaa@qq.com]
Returned connection 1551945522 to pool.
=====
Cache Hit Ratio [com.nl.dao10.UserMapper]: 0.5
true
15.5 缓存原理
15.6 自定义缓存(ehcache)
ehcache
- EhCache 是一个纯Java的进程内缓存框架。
- Ehcache是一种广泛使用的开源Java分布式缓存。
使用
-
添加依赖
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
-
编写配置文件
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <diskStore path="./tmpdir/Tmp_EhCache"/> <defaultCache eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> </ehcache>
-
引用
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
上一篇: springboot整合