文章目录
1、简介
什么是MyBatis?
- MyBatis 是一款优秀的持久层框架
- 它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
持久层的理解
所谓的持久层就是把数据可以永久的保存存储到设备中,不像放到内存中那样断电就消失,
为什么需要Mybatis?
- 帮助程序员将数据存入到数据库中
- 为了方便
- 传动的JDBC代码过于复杂,比如数据的建立连接,取出的封装…Mybatis使得简化,自动化
特点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql
2、第一个Mybatis程序
搭建环境
创建一个mybatis数据库,并建立user表,添加三条信息
建立一个普通的Maven项目,并删除src目录,作为父工程
导入Maven依赖
<!-- 导入依赖-->
<dependencies>
<!--Mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--mysql连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--Junit测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
创建一个子模块,这样子模块依赖可以继承父工程
编写Mybatis核心配置文件
在resoureces目录下建立一个mybatis-config.xml配置文件
从Mybatis官方文档中复制下来,修改diriver,url,username,password
<?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=false&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 每一个Mapper.xml都需要在Mybatis核心配置文件中注册! -->
<mappers>
<mapper resource="com/hs_vae/dao/UserMapper.xml"/>
</mappers>
</configuration>
编写Mybatis工具类
/**
* Mybatis工具类,通过SqlSessionFactory工厂生产SqlSession
*/
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
//获取sqlSessionFactory对象
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 有了sqlSessionFactory对象就可以从中获得SqlSession实例
* SqlSession里包含了数据库执行sql命令所需的所有方法
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
编写代码
建立User实体类
对应的是user表
package com.hs_vae.pojo;
/**
* User实体类
*/
public class User {
private int id;
private String name;
private String pwd;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public User() {
}
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
}
创建UserMapper接口
public interface UserMapper {
List<User> getUserList();
}
创建UserMapper接口实现的xml文件
接口实现类由原来的UserDaoImpl转变成为一个Mapper配置文件
新建一个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.hs_vae.dao.UserMapper">
<!--id为执行的方法,resultType为返回的类型-->
<select id="getUserList" resultType="com.hs_vae.pojo.User">
select * from mybatis.user
</select>
</mapper>
由于Maven约定大于配置,放在java目录下的UserMapper.xml文件不能被导出,要在pom.xml文件里面配置resource
<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>
测试
public class UserDaoTest {
@Test
public void getUsers(){
//第一步,获得SQLSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步,执行sql
UserDao userDao = sqlSession.getMapper(UserMapper.class);
List<User> userList = userDao.getUserList();
for (User user : userList) {
System.out.println(user);
}
//第三步,关闭SqlSession
sqlSession.close();
}
}
测试后,报错,解决方案:mybatis-config.xml里修改url将useSSL=false,我是没有该mysql配置文件ssl用不了
修改后,再测试,查询成功
3、CRUD(增删改查)
3.1、几个属性
- namespace:绑定一个对应的Dao/Mapper接口,包名要和Dao/Mapper接口的包名保持一致
- id:就是对应的namespace中的方法名,相当于重写方法
- resultType:sql语句执行的返回值
- parameterType:参数类型
3.2、select
案例:查询指定Id的User信息
UserMapper编写一个通过id查询的抽象方法
public interface UserMapper {
List<User> getUserList();
//通过id查询user
User getUserById(int id);
}
UserMapper.xml添加select标签
<!--
select查询指定id的user语句,
id对应方法名字,resultType对应返回类型
parameterType代表参数类型
-->
<select id="getUserById" parameterType="int" resultType="com.hs_vae.pojo.User">
select * from mybatis.user where id = #{id}
</select>
测试类,添加一个Junit单元测试
@Test
public void getUserById(){
//第一步,获得SQLSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步,执行sql
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(2);
System.out.println(userById);
//第三步,关闭SqlSession
sqlSession.close();
}
运行结果,查询到了id为2的user
3.3、insert
UserMapper接口添加insertUser()抽象方法
public interface UserMapper {
//查询所有用户
List<User> getUserList();
//查询一个用户
User getUserById(int id);
//插入一个用户
int insertUser(User user);
}
UserMapper.xml添加insert标签
<insert id="insertUser" parameterType="com.hs_vae.pojo.User">
insert into mybatis.user(id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
测试类中,添加一个Junit单元测试
注意:增删改语句都要提交事务:sqlSession.commit()
@Test
public void insertUser(){
//第一步,获得SQLSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步,执行sql
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.insertUser(new User(4,"vae","666454"));
//第四步,提交事务
sqlSession.commit();
System.out.println("插入成功");
//第五步,关闭sqlSession
sqlSession.close();
}
启动测试,插入成功
3.4、delete
UserMapper接口添加deleteUser()抽象方法
public interface UserMapper {
//查询所有用户
List<User> getUserList();
//查询一个用户
User getUserById(int id);
//插入一个用户
int insertUser(User user);
//删除一个用户
int deleteUser(int id);
}
UserMapper.xml添加delete标签
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id};
</delete>
测试类中,添加一个Junit单元测试,删除id=4的用户
@Test
public void deleteUser(){
//第一步,获得SQLSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步,执行sql
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(4);
if (i>0){
System.out.println("删除该用户成功");
//第四步,提交事务
sqlSession.commit();
}else {
System.out.println("删除失败");
}
//第五步,关闭sqlSession
sqlSession.close();
}
run该方法,删除成功!
3.5、update
UserMapper接口添加updateUser()抽象方法
public interface UserMapper {
//查询所有用户
List<User> getUserList();
//查询一个用户
User getUserById(int id);
//插入一个用户
int insertUser(User user);
//删除一个用户
int deleteUser(int id);
//修改一个用户
int updateUser(User user);
}
UserMapper.xml添加update标签
<update id="updateUser" parameterType="com.hs_vae.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id};
</update>
测试类中,添加一个Junit单元测试,修改id=3的用户信息
@Test
public void updateUser(){
//第一步,获得SQLSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步,执行sql
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int result = mapper.updateUser(new User(3, "薛之谦", "454455"));
if (result>0){
System.out.println("修改该用户成功");
//第四步,提交事务
sqlSession.commit();
}else {
System.out.println("修改失败");
}
//第五步,关闭sqlSession
sqlSession.close();
}
run该方法,修改该用户信息成功!
3.6、Map的巧用
当我们的实体类,或者数据库中的表,字段或者参数过多,我们可以考虑使用Map
下面举一个例子:
当我们去修改密码时候,我们只要知道用户的id就可以了,没有必要知道他的名字,像上面那样使用实体类User当参数类型的话,要将id,name,pwd全部写出来,而且名字必须和实体类保持统一,很麻烦
而我们使用Map当参数类型时,只需要传递其中的key,而且key的名字可以随意取,不用在为了修改密码写上用户的名字,等字段多了,很是头疼
先在UserMapper接口添加以Map为类型的抽象方法
//修改一个用户
int updateUser2(Map<String,Object> map);
在UserMapper.xml里添加update标签
对比于上面的update标签,传递的是Map里的key,发现这里的key可以随意命名,并且不需要在写多余的字段(name)了
<update id="updateUser2" parameterType="map">
update mybatis.user set pwd=#{userPwd} where id=#{userId};
</update>
测试类中,添加一个Junit单元测试方法
@Test
//使用Map当参数类型
public void updateTest2(){
//第一步,获得SQLSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//第二步,执行sql
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
//将需要修改的用户id和修改后的密码存入map中
map.put("userPwd","456789");
map.put("userId",1);
int i = mapper.updateUser2(map);
if (i>0){
System.out.println("修改该用户成功");
//第四步,提交事务
sqlSession.commit();
}else {
System.out.println("修改失败");
}
//第五步,关闭sqlSession
sqlSession.close();
}
run该方法,测试结果,id=1的用户密码成功被修改,name不受影响
总结:
Map传递参数,直接在sql中取出key即可 paramType=“map”
对象传递参数,直接在sql中取对象的属性即可,paramType=“Object”
只有一个基本类型参数的情况下,可与直接在sql中取到
多个参数用Map,或者注解
3.7、模糊查询
案例:给出user表,里面有两个姓"胡"的用户,要求能够查出姓为"胡"的全部用户信息
有两种方法
方法一:直接在sql拼接中使用通配符
<select id="getUserList" resultType="com.hs_vae.pojo.User" >
select * from mybatis.user where name like "%"#{value}"%";
</select>
方法二:在java代码里使用 % %通配符
List<User> userList = userMapper.getUserList("%胡%");
查询结果
4、配置解析(存在优化)
4.1、核心配置文件
- mybatis-config.xml
- MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
- 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
- configuration(配置)
所有的标签必须按照以上顺序写properties最顶端,mapper最底部
4.2、环境配置(environments)
Mybatis可以配置成适应多种环境
不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。
Mybatis默认的事务管理器就是JDBC,一共有两个: JDBC和MANAGED
默认的数据源:POOLED
4.3、属性 (properties)
我们可以通过properties属性来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。(mysql.properties)
在resource目录下建立mysql.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456
在核心配置文件mybatis-config.xml中引入,(要放在顶部)
<!--引入外部配置文件-->
<properties resource="mysql.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</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>
总结:
- 可以直接引入外部文件
- 也可以在其中增加一些外部文件里没有的属性配置
- 如果两个文件有同一个字段,优先使用外部配置文件的
4.4、类型别名(typeAliases)
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写
例如:将com.hs_vae.pojo包下的User实体类,设置别名为user
<!--设置别名-->
<typeAliases>
<typeAlias type="com.hs_vae.pojo.User" alias="user"/>
</typeAliases>
设置后,可以在UserMapper.xml的resultType中使用别名user
<select id="getUserList" resultType="user" >
select * from mybatis.user ;
</select>
也可以扫描指定的包,实体类类名按照首字母小写设置别名,但是必须保证两者小写或大写是一样的
<!--设置别名-->
<typeAliases>
<!-- 扫描实体类的包 -->
<package name="com.hs_vae.pojo"/>
</typeAliases>
扫描后的包下的实体类可以使用@Alias注解设置自定义的别名
4.5、设置(settings)
下面列举一些重要的设置
日志实现设置
缓存机制
4.6、映射器 (mappers)
MapperRegistry:注册绑定我们的Mapper文件
有以下几种方式注册绑定
方式一:使用resource资源引用注册绑定 (推荐)
<mappers>
<mapper resource="com/hs_vae/dao/UserMapper.xml"/>
</mappers>
方式二:使用class文件绑定注册
<mappers>
<mapper class="com.hs_vae.dao.UserMapper"/>
</mappers>
注意:接口和它的Mapper.xml配置文件必须同名且必须在同一个包下
方式二:使用扫描包进行注入绑定注册
<mappers>
<package name="com.hs_vae.dao"/>
</mappers>
注意:接口和它的Mapper.xml配置文件必须同名且必须在同一个包下
4.7、生命周期和作用域(scope)
生命周期和作用域,是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder
- 一旦创建了SqlSessionFactory,就不再需要它了
- 局部变量
SqlSessionFactory
类似于数据库连接池
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
SqlSessionFactory最佳作用域是应用作用域
最简单的就是使用单例模式或者静态单例模式
SqlSession
- 连接到连接池的一个请求
- SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
- 用完之后需要赶紧关闭,否则资源被占用
这里的每一个Mapper代表一个具体的业务
5、属性名和字段名不一致问题
问题
数据库的User表的字段
User实体类的属性
private int id;
private String name;
private String password;
两个密码是不一样的,一个是pwd,一个是password,这种情况下会出现查询密码为空的问题
解决方案:可以使用resultMap结果集映射
resultMap
结果集映射
id name pwd
id name password
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--column代表数据库中的字段,property代表实体类的属性-->
<result property="password" column="pwd"/>
</resultMap>
<select id="getUserById" resultMap="UserMap">
select * from mybatis.user where id = #{id};
</select>
测试,查询到了密码
ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了
6、日志
6.1、日志工厂
如果一个数据库操作,出现了异常,我们需要去排错,就需要用到日志
有以下一些日志
- SLF4J
- LOG4J
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING
- NO_LOGGING
在Mybatis中具体使用哪一个日志实现,在设置settings中设定
STDOUT_LOGGING标准日志输出
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
注意这里的name和value都必须和官方文档保持统一,注意不要有空格
使用日志后测试,出现了日志信息
6.2、LOG4J
什么是LOG4J?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们也可以控制每一条日志的输出格式
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
- 这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码
导入依赖
<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/hs.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{yyyy-MM-dd HH:mm:ss}][%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
配置log4j为日志的实现
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
log4j的使用,直接测试
生成的日志文件发现打不开,文件提示问号
原因是在mybatis-config.xml配置文件中,有Package扫描包,需要将它删掉
<!--设置别名-->
<typeAliases>
<typeAlias type="com.hs_vae.pojo.User" alias="user"/>
<package name="com.hs_vae.pojo"/>
</typeAliases>
删掉后测试,可以成功打开
简单使用
在要使用log4j的类中,导入的是org.apache.log4j.Logger;
日志对象,参数为当前类的class
static Logger logger = Logger.getLogger(UserMapper.class);
日志级别
@Test
public void testLog4j(){
logger.info("info:已经进入了testLog4j");
logger.debug("debug:已经进入了testLog4j");
logger.error("error:已经进入了testLog4j");
}
测试后,日志信息都追加到了日志文件里面
7、分页
分页是为了减少数据的处理量
7.1、使用Limit分页
在UserMapper接口创建分页的抽象方法(使用Map传递参数)
public interface UserMapper {
//查询一个用户
User getUserById(int id);
//分页查询
List<User> getUserLimit(Map<String,Integer> user);
}
UserMapper.xml编写select标签
<!--结果集映射-->
<resultMap id="UserMap" type="User">
<!--column代表数据库中的字段,property代表实体类的属性-->
<result property="password" column="pwd"/>
</resultMap>
<select id="getUserLimit" parameterType="map" resultMap="UserMap">
select * from mybatis.user limit #{startIndex},#{pageSize};
</select>
测试类里,添加一个Junit单元测试
@Test
public void testLimit(){
//获取SqlSession对象
SqlSession sqlSession = MybatisUtils.getSqlSession();
//执行sql
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap<String, Integer>();
map.put("startIndex",1);
map.put("pageSize",2);
List<User> userLimit = mapper.getUserLimit(map);
for (User user : userLimit) {
System.out.println(user);
}
sqlSession.close();
}
测试,查询第0页的两条信息
8、使用注解开发
查询注解
@Select("select * from mybatis.user")
List<User> getUser();
不过简单的查询可以使用注解,如果复杂的语句就使用xml配置比较合适
CRUD
在工具类中将自动提交事务设置为true
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession(true);
}
UserMapper接口
//使用注解查询一个用户
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id1);
//使用注解增加一条数据
@Insert("insert into user(id,name,pwd) values (#{id},#{name},#{password})")
int addUser(User user);
//使用注解修改一条数据
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
//使用注解删除一条数据
@Delete("delete from user where id=#{uid}")
int deleteUser(@Param("uid")int id);
测试
@Test
public void selectTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.getUserById(4);
sqlSession.close();
}
@Test
public void addUserTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.addUser(new User(5,"yu","123132"));
sqlSession.close();
}
@Test
public void updateTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(5, "ok", "1231123"));
sqlSession.close();
}
@Test
public void deleteTest(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.deleteUser(5);
sqlSession.close();
}
使用注解进行简单的操作减少了很多JDBC的代码,变得很简单,不过这仅限于比较简单的SQL语句
关于@Param{}注解
- 基本类型的参数或者String类型,需要加上
- 引用类型不需要加
- 如果只有一个基本类型的话,可以忽略,但是建议都加上
- 我们在Sql引用的就是我们这里的@Param{“uid”}中设定的属性
#{}和${}区别
- #{}是预编译处理,$ {}是字符串替换。
- MyBatis在处理#{}时,会将SQL中的#{}替换为?号,使用PreparedStatement的set方法来赋值;MyBatis在处理 $ { } 时,就是把 ${ } 替换成变量的值。
- 使用 #{} 可以有效的防止SQL注入,提高系统安全性。
9、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.
简单的来说,就是使用注解让你的Java代码变得更简单,比如实体类中get和set等方法以及有参无参构造方法通过注解给你生成
在IDEA中安装lombok插件,完整完成后重启IDEA
有以下注解可供使用
@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
安装后添加lombok的Maven依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
使用LomBok
没使用LomBok之前,一般都是这样编写实体类
/**
* User实体类
*/
public class User {
private int id;
private String name;
private String password;
public User() {
}
public User(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
使用LomBok后,LomBok让代码变得非常简洁
/**
* User实体类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
@Data 自动给你编写get、Set、equals等方法
@AllArgsConstructor 有参的构造方法
@NoArgsConstructor 无参的构造方法
LomBok有一定的弊端
- JDK从Java 8升级到Java 11时,Lombok不能正常工作
- 可读性差,而且不安全
- 代码耦合度增加,当你使用Lombok来编写某一个模块的代码后,其余依赖此模块的其他代码都需要引入Lombok依赖,同时还需要在IDE中安装Lombok的插件
10、多对一处理
比如好几个学生对一个老师
建立一个student表和一个teacher表,并且将teacher的id设置为student的外键,使其关联
案例需求:查询所有的学生信息,以及对应的老师信息
编写StudentMapper接口
public interface StudentMapper {
List<Student> getStudent2();
}
将Student和Teacher设置别名
<typeAliases>
<package name="com.hs_vae.pojo"/>
</typeAliases>
编写Mapper
<!-- 按照结果嵌套处理 -->
<select id="getStudent2" resultMap="StudentTeacher">
select s.id sid,s.name sname,t.name tname
from student s,teacher t
where s.tid=t.id;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
测试,编写一个Junit单元测试类
@Test
public void test02(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> student2 = mapper.getStudent2();
for (Student student : student2) {
System.out.println(student);
}
sqlSession.close();
}
11、一对多处理
一个老师手上有多个学生
案例需求:查询一个指定的老师以及对应的所有学生
Teacher实体类和Student实体类
@Data
public class Teacher {
private int tid;
private String name;
private List<Student> students;
}
@Data
public class Student {
private int id;
private String name;
}
TeacherMapper接口
public interface TeacherMapper {
//查询一个指定的老师和对应的所有学生
Teacher getTeacher(@Param("tid") int id);
}
编写sql
<select id="getTeacher" resultMap="StudentTeacher">
select t.id tid,t.name tname,s.id sid,s.name sname
from student s,teacher t
where t.id=s.tid;
</select>
<resultMap id="StudentTeacher" type="Teacher">
<result property="tid" column="tid"/>
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
Junit单元测试
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher);
sqlSession.close();
}
测试结果
总结
- 关联使用association (多对一)
- 集合使用collection (一对多)
- javaType和ofType
- javaType 用来指定实体类中属性的类型
- ofType用来指定映射到List或者集合的pojo类型,即泛型约束类型
12、动态SQL
动态SQL就是根据不同的条件生成不同的SQL语句
如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
包含了以下标签
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
12.1、准备工作
创建一个博客表blog
博客实体类
/**
* 博客实体类
*/
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
这里的创建时间属性和blog表字段不对应
可以在核心配置文件中设置,将create_time映射成createTime
<!--是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
创建一个随机生成博客id的工具类
public class UUIDUtils {
public static String getUUID(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
12.2、IF
BlogMapper接口
public interface BlogMapper {
//动态SQL查询(IF)
List<Blog> getBlogIF(Map<String,Object> map);
//插入数据
int addBlog(Blog blog);
}
编写BlogMapp.xml动态SQL
当title为空时候或作者为空时候查询全部,title不为空,查询该title信息,可以动态查询
<select id="getBlogIF" 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>
<insert id="addBlog" parameterType="blog">
insert into blog(id,title,author,create_time,views) values(#{id},#{title},#{author},#{createTime},#{views});
</insert>
测试,编写一个Junit测试并插入数据
@Test
public void testIF(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map= new HashMap<String, Object>();
map.put("title","Mybatis学习");
List<Blog> blogIF = mapper.getBlogIF(map);
for (Blog blog : blogIF) {
System.out.println(blog);
}
sqlSession.close();
}
@Test
public void testAddBlog(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(UUIDUtils.getUUID());
blog.setAuthor("胡帅");
blog.setTitle("Mybatis学习");
blog.setCreateTime(new Date());
blog.setViews(123);
mapper.addBlog(blog);
blog.setTitle("Spring学习");
blog.setCreateTime(new Date());
blog.setViews(1000);
mapper.addBlog(blog);
blog.setTitle("SpringMVC学习");
blog.setCreateTime(new Date());
blog.setViews(500);
mapper.addBlog(blog);
sqlSession.close();
}
插入后的blog表,有三条blog信息
title不为空时候,查询title="Mybatis学习"的blog信息
title为空时,查询全部blog信息
12.3、choose(when、otherwise)
有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。
针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
BlogMapper接口添加一个抽象方法
//动态SQL(choose,when,otherwise)
List<Blog> getBlogChoose(Map<String,Object> map);
BlogMapper.xml里添加select,当title
<select id="getBlogChoose" parameterType="map" resultType="Blog">
select * from blog
where 1=1
<choose>
<when test="title!=null">
and title=#{title}
</when>
<when test="views!=null">
and views=#{views}
</when>
<otherwise>
and id=#{id}
</otherwise>
</choose>
</select>
测试类
@Test
public void testChoose(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("id","2e4c4a542cdf45fbbfccd8fb3407223d");
map.put("title","Mybatis学习");
map.put("views",1000);
List<Blog> blogChoose = mapper.getBlogChoose(map);
for (Blog blog : blogChoose) {
System.out.println(blog);
}
sqlSession.close();
}
只传入id=“2e4c4a542cdf45fbbfccd8fb3407223d”,对应上面的blog表,那么就可以查询到三条信息
传入id="2e4c4a542cdf45fbbfccd8fb3407223d"和title="Mybatis学习"后
当传入title="Mybatis学习"和views=1000时,只查询到了符合title的信息,说明当when有一个条件符合时,就结束了,不会在执行下面的元素
总结:可以利用choose元素从多个条件里面选择一个条件去执行,一旦有一个条件符合就会结束,类似于switch语句
12.4、where
前面是if和choose元素都要设置where后面的1=1,是为了避免出现错误
<select id="getBlogIF" parameterType="map" resultType="Blog">
select * from blog
where
<if test="title!=null">
and title=#{title}
</if>
<if test="author!=null">
and author=#{author}
</if>
</select>
如果没有匹配到任何条件,那么SQL变成这样:
select * from blog
where
这个查询显然是错误的
如果匹配到了第二个条件,那么SQL变成这样:
select * from blog
where
and author=?
这个查询也是错误的
这个问题我们可以使用where属性来解决这问题
先在BlogMapper接口添加一个抽象方法
//动态SQL(where)
List<Blog> getBlogWhere(Map<String,Object> map);
在BlogMapper.xml里编写sql
<select id="getBlogWhere" resultType="Blog" parameterType="map">
select * from blog
<where>
<if test="title!=null">
title=#{title}
</if>
<if test="views!=null">
and views=#{views}
</if>
</where>
</select>
测试
@Test
public void testWhere(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("title","Mybatis学习");
map.put("views",1000);
List<Blog> blogChoose = mapper.getBlogWhere(map);
for (Blog blog : blogChoose) {
System.out.println(blog);
}
sqlSession.close();
}
传入的是title="Mybatis学习"和views=1000查看blog表,没有同时满足这两个条件这条信息
单独传入views=1000,可以发现where属性自动将and给删除掉了,
总结:
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。
若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
12.5、set
用于动态更新Update语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列
同理和上面的一样,添加接口抽象方法,就不在写了
BlogMapper.xml编写update
<update id="updateBlogSet" parameterType="map">
update blog
<set>
<if test="title!=null">
title=#{title},
</if>
<if test="views!=null">
views=#{views}
</if>
</set>
where id=#{id}
</update>
set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
测试
@Test
public void testSet(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Map<String,Object> map = new HashMap<String, Object>();
map.put("title","Mybatis学习");
map.put("views",1000);
map.put("id","2e4c4a542cdf45fbbfccd8fb3407223d");
mapper.updateBlogSet(map);
sqlSession.close();
}
更新了三条符合id的信息
12.6、SQL片段
有时候,我们可能将一些功能的部分抽取出来,方便复用,可以使用SQL标签
- 使用SQL标签抽取公共的部分
<!--SQL片段-->
<sql id="if-title-views">
<if test="title!=null">
title=#{title}
</if>
<if test="views!=null">
and views=#{views}
</if>
</sql>
- 在需要使用的地方使用include标签引用即可
<select id="getBlogWhere" resultType="Blog" parameterType="map">
select * from blog
<where>
<include refid="if-title-views"></include>
</where>
</select>
注意:
- 最好基于单表来定义SQL片段
- SQL片段不要存在where标签
13、缓存
13.1、简介
什么是缓存?
缓存是存在内存中的临时数据
将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
为什么要使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率
什么样的数据能使用缓存?
- 经常查询并且不经常修改的数据
13.2、Mybatis缓存
- MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制,极大的提升查询效率
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启,它仅仅对一个会话中的数据进行缓存(SqlSession级别,也称本地缓存)
- 二级缓存需要手动开启和配置,它是基于namespace级别的缓存
- 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现该接口来自定义二级缓存
13.3、一级缓存
一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库获取
测试:
user表
开启日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
在一个SqlSession中查询两次相同记录
@Test
public void test01(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserById(1);
System.out.println(user1);
User user2 = mapper.getUserById(1);
System.out.println(user2);
//判断查询后两个user是否相等
System.out.println(user1==user2);
sqlSession.close();
}
查看日志输出
发现查询两次id=1的user,发现sql只执行一次
缓存失效的情况
- 增删改操作,可能会改变原来的数据库,必定会刷新缓存
- 查询不同的东西
- 查询不同的Mapper
- 手动清除 SqlSession.clearCache()
小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间
13.4、二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
- 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
- 工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的namespace查出的数据会放在自己对应的缓存中
开启全局缓存(核心配置文件中)
<!--开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
在要使用二级缓存的Mapper中开启
<!--在当前的UserMapper.xml中使用二级缓存-->
<cache/>
也可以自定义一些参数
<!--在当前的UserMapper.xml中使用二级缓存-->
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"
/>
测试,Junit单元测试
@Test
public void test01(){
//创建第一个SqlSession
SqlSession sqlSession1 = MybatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.getUserById(1);
System.out.println(user1);
//关掉SqlSession1,相当于结束会话
sqlSession1.close();
//获取第二个SqlSession
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.getUserById(1);
System.out.println(user2);
sqlSession2.close();
//判断查询后两个user是否相等
System.out.println(user1==user2);
}
测试结果,第一个SqlSession关掉后,相当于结束了一级缓存,但是里面查询的数据放到了二级缓存中,可以供第二个SqlSession使用
小结:
- 只要开启了二级缓存,在同一个Mapper下就有效,上面两个SqlSession都在同一个UserMapper下
- 所有的数据都会放在一级缓存中
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
13.5、自定义缓存-ehcache
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。
导入Maven ehcache依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.0.3</version>
</dependency>
在Mapper中指定使用自定义的ehcache缓存实现
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
ehcache.xml配置文件
<?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>
笔记下载
关注微信公众号"自定义的Vae",下载Mybatis学习笔记