MyBatis
一、简介
1、什么是MyBatis
- MyBatis 是一款优秀的持久层框架
- 它支持自定义 SQL、存储过程以及高级映射。
- MyBatis 避免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
- MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
- MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。
- 2013年11月迁移到Github。
2、如何获得MyBatis?
maven仓库
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>GitHub:
3、持久化(是一个动作)
数据持久化
- 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
- 内存:断电即失
- 数据库(jdbc),io文件持久化
- 生活:冷藏。
为什么需要持久化
- 有一些对象,不能让他丢掉
- 内存太贵
4、持久层
Dao层、Service层、Controller层
- 完成持久化工作的代码
- 层界限十分明显
5、为什么需要MyBatis?
帮助程序员将数据存入数据库中
方便
传统JDBC代码太复杂了。简化,框架。自动化
不用MyBatis也可以。使用框架更容易上手。技术没有高低之分
优点
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
- 最重要的一点:使用的人多(Spring,Spring MVC,Spring Boot)
二、第一个MyBatis程序
思路:搭建环境 -> 导入MyBatis -> 编写代码 ->测试!
1、搭建环境
搭建数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14CREATE TABLE `user` (
`id` INT ( 20 ) NOT NULL PRIMARY KEY,
`name` VARCHAR ( 30 ) DEFAULT NULL,
`password` VARCHAR ( 30 ) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET(utf8);
INSERT INTO `user`(id,`name`,`password`) VALUES
(1,'张三','123'),
(2,'李四','123'),
(3,'李白','123'),
(4,'韩信','123')
新建项目:
新建一个普通的Maven项目
删除src目录
导入Maven依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<!--junit的jar包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<!--mysql的jar包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<!--MyBatis的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
2、创建一个模块
编写mybatis的核心配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<configuration><!--核心配置文件-->
<environments default="development"><!--环境-->
<environment id="development">
<transactionManager type="JDBC"/><!--事务管理-->
<dataSource type="POOLED">
<property name="driver" value="${com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="$jdbc:mysql://localhost:3306/mybatis? serverTimezone=UTC&useSSL=true&useUnicode=true&characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
编写mybatis工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28//sqlSessionFactory-->sqlSession
public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
InputStream inputStream = null;
try {
//使用mybatis第一步,sqlSessionFactory对象
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
3、编写代码
实体类
1
2
3
4
5
6
7public class User {
private Integer id;
private String name;
private String password;
/* 一些构造函数,get和set方法还有toString方法 */
}Dao接
1
2
3public interface UserDao {
List<User> getUserList();
}接口实现类(由原来的UserDaoImpl转变为一个Mapper配置文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--namespace绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.hngy.dao.UserDao">
<!--查询语句 ,id对应方法名字 ,resultType返回值的类型-->
<select id="getUserList" resultType="com.hngy.pojo.User">
select *from mybatis.user
</select>
</mapper>
4、测试
注意点:(以下错误需要在mybatis的核心文件中去配置)
1
Type interface com.hngy.dao.user.UserDao is not known to the MapperRegistry.
MapperRegistry.是什么
- 核心配置文件中注册mappers
junit测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testEmployeeDao() {
SqlSession sqlSession = null;
try {
sqlSession = MyBatisUtil.getSqlSession();
EmployeeDao mapper = sqlSession.getMapper(EmployeeDao.class);
mapper.selectAll().forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
assert sqlSession != null;
sqlSession.close();
}
}
5、可能会遇到的问题
配置文件没有注册:(写在mybatis的核心配置文件的configuration标签中,如需要再注册的话,不需要再添加mappers标签了,只需要在其中添加mapper标签)
1
2
3
4<!--每一个Mapper.xml都需要在mybatis核心配置文件中注册 ,resource:文件的路径,从Java包后开始写-->
<mappers>
<mapper resource="com/hngy/dao/user/UserMapper.xml"/>
</mappers>绑定接口错误:
方法名不对:(方法名为id的值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--namespace绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.hngy.dao.user.UserDao">
<!--查询语句 ,id对应方法名字 ,-->
<select id="getUserList" resultType="com.hngy.pojo.User">
select *from mybatis.user
</select>
</mapper>返回类型不对:返回值的类别为(resultType中的类型)
Maven导出资源问题(在pom.xml文件中加入如下代码):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<!--在build中配置resources, 来防止我们资源导出失败问题-->
<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>
三、CRUD
1、namespace
namespace需要和Dao/mapper接口的包名一致!
1
2
3
4
5
6
7
<!--namespace绑定一个对应的Dao/Mapper接口-->
<mapper namespace="com.hngy.dao.user.UserMapper"> </mapper>
2、select
选择,查询语句
- id:就是对应的namespace中的方法名;
- resultType:Sql语句执行的返回值!
- parameterType:参数类型,可以省略,
- 参数的类型是全是一个类型对应一个值(int id , String name)那么sql中参数的值为: #{id} #{name}
- 参数的类型是一个类对应一张表的情况:参数的变量可以直接取该类中定义的变量
- 如:如下为User类中由(int id,String name),那么可以取值为:#{id} #{name}
1
2
3
4
5
6
7
8
9<!--查询语句 ,id对应方法名字 ,-->
<select id="getUserList" resultType="com.hngy.pojo.User">
select *from mybatis.user
</select>
<!--根据id查询用户的基本信息-->
<select id="getUserById" resultType="com.hngy.pojo.User" parameterType="int">
select *from mybatis.user where id=#{id}
</select>
多条记录封装一个map:Map
1 | //多条记录封装一个map:Map<Integer,Employee>:键是这条记录的主键,值是记录封装后的javaBean |
模糊查询
使用
$
进行接收值
不安全,会出现SQL注入
1 | <select id="getById" resultType="java.util.Map"> |
使用
#
接收字符串和concat
函数拼接字符串
1 | <select id="getById" resultType="java.util.Map"> |
使用
""
将%
圈起来
推荐使用
1 | <select id="getById" resultType="java.util.Map"> |
3、获取主键的值
获取自增主键的值
- 获取自增主键的值
- mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys();
- useGeneratedKeys=”true”;使用自增主键获取主键值策略 ==(仅适用于 insert 和 update)==
- keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
1 | <insert id="addUser" useGeneratedKeys="true" keyProperty="id" databaseId="MySQL"> |
获取非自增主键的值
==获取非自增长的值需要使用到Oracle,由于没有环境配置,所以不能测试,有需要上网查询==
4、Insert
1 | <!--添加用户的信息 , 对象中的属性可以直接取出来--> |
5、Update
1 | <!--修改用户的基本信息--> |
6、Delete
1 | <!--根据id删除用户--> |
7、增删改的返回值问题
==增删改可以定义返回值类型,直接填写接口的返回值类型,mybatis会自己转换,只能填写(Integer、void、Long、Boolean==
1 | Boolean insertUser(User user); |
8、注意点(事务添加)
在增删改需要添加事务(sqlSession.commit();)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testDeleteUser() {
//第一步:获取sqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//第二步:执行sql(UserMapper.xml相当于UserDao的实现类)
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.deleteUser(5));
//提交事务
sqlSession.commit();
sqlSession.close();
}
9、分析错误
- 标签不要匹配错
- resource绑定mapper,需要使用路径(每个文件夹需要使用 / 隔开,而不是 . )
- 程序配置文件必须符合规范
- 空指针异常,没有注册资源
- 输出的xml文件中存在中文乱码问题:统一文件的字符编码集问UTF-8
- maven资源没有导出问题:具体解决方式在该笔记的(二、5中有具体的解决方案)
10、Map
假设我们的实体类,或者数据库中的表,字段或者过多,我们应当考虑Map
当实体类中的属性过多的时候,且sql的参数不需要这么多的参数的时候可以考虑Map来实现
传入的类型是实体类的话,实体类中的所有参数都需要赋值,而Map不需要,需要什么值的话就在Map中传入需要的值
添加
Java代码
1
2
3
4
5
6/**
* 一次性添加多个用户
* @param map 所有用户的Map集合
* @return 返回修改的信息
*/
int addUser(Map<String, Object> map);xml配置文件
1
2
3
4<!-- 参数的变量的值为map集合中的key的值 -->
<insert id="addUser" parameterType="map">
insert into mybatis.user(id,`name`,password) value (#{userId},#{userName},#{userPass})
</insert>测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void testAddUser2() {
//第一步:获取sqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//第二步:执行sql(UserMapper.xml相当于UserDao的实现类)
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userId" , 61);
map.put("userName" , "典韦");
map.put("userPass" , "123");
System.out.println(mapper.addUser2(map));
//提交事务
sqlSession.commit();
sqlSession.close();
}
修改
Java代码:
1
2
3
4
5
6
7/**
* 修改用户
*
* @param map 用户修改的新的信息
* @return 修改成功返回一个大于0的数字,修改失败返回0
*/
int updateUser2(Map<String,Object> map);xml配置文件:
1
2
3
4<!--修改用户的基本信息-->
<update id="updateUser2" parameterType="map">
update mybatis.user set `name`=#{userName} , password=#{userPass} where `id`=#{userId}
</update>测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void testUpdateUser2() {
//第一步:获取sqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//第二步:执行sql(UserMapper.xml相当于UserDao的实现类)
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("userId" , 61);
map.put("userName" , "典韦");
map.put("userPass" , "123");
System.out.println(mapper.updateUser2(map));
//提交事务
sqlSession.commit();
sqlSession.close();
}
Map使用
- Map传递参数,直接在sql中取出key即可【parameterType=”map”】
- 对象传递参数,直接在sql中去对象的属性即可【parameterType=”Object”】
- 自由一个基本参数类型的情况下,可以直接在sql中取到
- 多个参数用Map或注解
模糊查询
模糊查询怎么写
Java代码执行的时候传入通配符,在传入参数的时候传入“ % ”,此方法容易引发sql注入问题(不建议)
1
List<User> userLike = mapper.getUserLike("%李%");
在sql拼接的时候使用通配符,在sql语句中将 “ % ” 加入进去
1
select *from mybatis.user where `name` like "%"#{value}"%"
模糊查询的具体使用方法可以上网查询,在mybatis中使用模糊查询和写简单的sql语句有点不一样
四、配置解析
核心配置文件
- 文件名字(建议写如下名字):mybatis-config.xml
- MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:(必须按照如下顺序写)
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
- configuration(配置)
环境配置(environments)
environments:mybatis可以配置多种环境 ,default指定使用某种环境。可以达到快速切换环境。,比如连接不同的数据库(MySQL和Oracle),比如测试、开发、生产都连接不同的数据库
environment:配置一个具体的环境信息;==必须有两个标签(transactionManager和dataSource)==;配置多个environment,每个environment标签中设置不同的环境,根据标签中的id值来区分
不过要记住,尽管可以配置多个环境,但每个SqlSessionFactory示例只能选择一种环境
1 | <environments default="MySQL"><!--根据default中填写的值来指定连接哪个数据库--> |
MyBatis默认的事务管理器就是JDBC,连接池:POOLED
属性(properties)
properties标签必须放在XML文件的最上面
我们可以通过properties属性来实现引用配置文件
这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。亦可通过properties元素的子元素来传递。【db.propertires】
编写一个配置文件
1
2
3
4
5
6jdbc.driver=com.mysql.cj.jdbc.Driver
#如果是MySQL8.0需要加上时区
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
#这里不能设置为username
jdbc.username=root
jdbc.password=root在核心配置文件中引入
可以直接引入外部文件
可以在其中增加一些属性配置
1
2
3
4
5<!--引入外部配置文件,此时在XML配置文件中设置了一部分的值,如果外部文件中有,会优先使用外部文件-->
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>如果两个文件有同一个字段,优先使用外部的properties文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!--引入外部配置文件-->
<properties resource="db.properties"/>
<!--注入外部文件的用户名和密码-->
<environments default="development"><!--环境-->
<environment id="development">
<transactionManager type="JDBC"/><!--事务管理为JDBC的事务管理-->
<dataSource type="POOLED"><!--连接池为:POOLED-->
<property name="driver" value="${driverClassName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
类型别名(typeAliases)
顺序:typeAlias > 注解 > 限定包名
类型别名可为 Java 类型设置一个缩写名字。
它仅用于 XML 配置,意在降低冗余的全限定类名书写。
1
2
3
4<!--可以给实体类启别名-->
<typeAliases>
<typeAlias type="com.hngy.pojo.User" alias="user"/>
</typeAliases>也可以指定一个包名,MyBatis会在包名下面搜索需要的JavaBean,比如:
扫描实体类的包,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。
1
2
3
4<!--可以给实体类启别名,该方式为:该包下所有的方法的别名为首字母小写-->
<typeAliases>
<package name="com.hngy.pojo"/>
</typeAliases>在实体类比较少的时候,使用第一种方式。
如果实体类十分多,建议使用第二种
第一种可以自定义(DIY)别名,第二种则不行,如果非要该,需要在实体类上增加注解
开启注解起别名需要将扫描实体类包开启,不开启的话会报错,找不到起的别名
1
2//需要在核心配置文件中将扫描实体类的包开启才能使用注解,两者需要同时使用
public class User {}
- 三者异同点:
- 三者所起的别名都不区分大小写
- 方式一和方式三都是给单个类起被名,方式二是批量起别名
- @Alias注解方式的优先级最高,当三者都存在时,只会读取标有@Alias注解的别名,除去@Alias注解外,其他二种方式的优先级相同,并且可以并存
- 补充 : 使用以上三种被名的情况,依旧可以使用全类名做类名
- 注意,使用注解给实体类起别名需要将扫描实体类的包开启,两者需要共同使用,如果单独使用扫描实体类的包则不需要将注解开启
设置(settings)
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。
![image-20210305221747077](C:\Users\李\AppData\Roaming\Typora\typora-user-images\image-20210305221747077.webp)
数据库厂商标识(databaseIdProvider)
==作用就是得到数据库厂商的标识(驱动getDatabaseProductName()),mybatis就能根据数据库厂商标识来执行不同的sql;==
1 | <databaseIdProvider type="DB_VENDOR"> |
根据不同的数据库厂商执行不同的SQL语句
==注意:需要在标签中使用 databaseId 属性来指定厂商标识符,当出现同一时刻有两条SQL语句需要执行的时候,此时以写了数据库标识的那条SQL语句为准,先执行==
1
2
3
4
5
6
7
8
9
10
11
12<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee">
select * from tbl_employee where id = #{id}
</select>
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee" databaseId="mysql">
select * from tbl_employee where id = #{id}
</select>
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee" databaseId="oracle">
select EMPLOYEE_ID id,LAST_NAME lastName,EMAIL email
from employees where EMPLOYEE_ID=#{id}
</select>
映射器(mappers)
MapperRegistry:注册绑定我们的Mapper文件
方式一:【推荐使用】
1
2
3
4<!--每一个Mapper.xml都需要在mybatis核心配置文件中注册 ,resource:文件的路径,从Java包后开始写-->
<mappers>
<mapper resource="com/hngy/dao/user/UserMapper.xml"/>
</mappers>方式二:使用class文件绑定注册:
注意点:
接口和它的Mapper配置文件必须同名
接口和它的Mapper配置文件必须在同一个包下
1
2
3
4<!--class:类的全路径-->
<mappers>
<mapper class="com.hngy.dao.user.UserMapper"/>
</mappers>方式三:使用扫描包进行注入绑定
1
2
3
4<!--每一个Mapper.xml都需要在mybatis核心配置文件中注册-->
<mappers>
<mapper class="com.hngy.dao"/>
</mappers>
注意点:
接口和它的Mapper配置文件必须同名
接口和它的Mapper配置文件必须在同一个包下
其他配置
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- mybatis-generator-core
- mybatis-plus
- 通用mapper
作用域(Scope)和生命周期
- 生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题
SqlSessionFactoryBuilder
一旦创建了SqlSessionFactory,就不在需要它了
作用:局部变量
SqlSessionFactory
说白了可以想象为:数据库连接池
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
SqlSessionFactory 的最佳作用域是应用作用域。
最简单的就是使用单例模式或者静态单例模式。
SqlSession
连接到连接池的一个请求
SqlSession的示例不是线程安全的,因此是不能被共享的,它的最佳的作用域是请求或方法作用域
使用完之后需要赶紧关闭,否则资源被占用
这里的每一个Mapper,就代表一个业务
传入参数的问题
1、单个参数
单个参数:mybatis不会做特殊处理,
- 取值:#{参数名 / 任意名}
2、多个参数
多个参数:mybatis会做特殊处理。
- 多个参数会被封装成 一个map,
- key:param1…paramN,或者参数的索引也可以
- value:传入的参数值
- 取值:#{ param1 , param2 }就是从map中获取指定的key的值;
3、命名参数
使用 @param 注解 明确指定封装参数时map的key;@Param(“id”)
4、其他
POJO
如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
- 取值: #{属性名}:取出传入的pojo的属性值
Map
如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
- 取值: #{key}:取出map中对应的值
TO
如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象
5、取值问题
public Employee getEmp(@Param(“id”)Integer id,String lastName)
1
取值:id==>#{id/param1} lastName==>#{param2}
public Employee getEmp(Integer id,@Param(“e”)Employee emp);
1
取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
特别注意:如果是Collection(List、Set)类型或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中。
public Employee getEmpById(List
ids); 1
2
3key:Collection(collection),如果是List还可以使用这个key(list)
数组(array)
取值:取出第一个id的值: #{list[0]}
五、解决属性名和字段名不一致的问题
1、问题
数据库中的字段
数据库对应的JavaBean的实体类对象(此时数据库中的字段和实体类中的字段不一样)
1
2
3
4
5
6public class User {
private Integer id;
private String name;
private String psd;
//一些对应的get和set方法、toString方法,构造函数等方法
}测试出现的问题:(不一样的字段出现查询为null)
1
User{id=1, name='张三', psd='null'}
2、解决方法
==使用resuleMap自定义结果集映射==
==注意:在映射结果集的时候,如果是主键id,尽量使用 id 标签来映射结果集,非主键的字段使用 result 标签来映射结果集,Java实体类使用association标签来映射,集合使用 collection 标签来映射结果集,Java实体类和集合的结果集映射的具体使用可以去看一对多和多对一的笔记==
- column:数据库中的字段
- property:实体类中的属性
起别名
1 | <!--根据id查询用户的基本信息--> |
resultMap(结果集映射,将JavaBean中的字段和数据库中的字段对应起来)
1 | id name password |
1 | <!--结果集映射--> |
- resultMap 元素是 MyBatis 中最重要最强大的元素。
- ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
- ResultMap最优秀的地方在于,虽然你已经对它相当了解了,但根本就不需要显示的用到他们
- 如果使用热resultMap,自定义结果集映射,那么最好把所有字段都映射
后续还会有多对一,一对多,多对多等
六、日志
1、日志工厂
如果一个数据库操作出现了异常,我们需要排错,日志就是最好的助手
以前:sout DeBug
现在日志工厂
SLF4J
LOG4J 【掌握】
LOG4J2
JDK_LOGGING
COMMONS_LOGGING
STDOUT_LOGGING 【掌握】
NO_LOGGING
在MyBatis中具体使用哪一个日志实现,在核心配置文件中设置
1
2、STDOUT_LOGGING
代码实现:
1
2
3
4<settings>
<!--标准日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>输出内容
1
2
3
4
5
6
7
8
9
10
11
12Opening JDBC Connection
Created connection 1784131088.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6a57ae10]
==> Preparing: select *from mybatis.user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, 张三, 123
<== Total: 1
User{id=1, name='张三', psd='123'}
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6a57ae10]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@6a57ae10]
Returned connection 1784131088 to pool.
3、LOG4J
什么是LOG4J ?
- Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
- 我们也可以控制每一条日志的输出格式;
- 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
- 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
导入log4j的包
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>log4j.properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24##将等级设置为DEBUG输出到控制台和文件定义在下面的代码
log4j.rootLogger=DEBUG, CONSOLE, FILE
## for console,输出到控制台的相关设置
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=%d{MM-dd-HH:mm:ss}[%c-%L][%t][%-4r] - %m%n
## for file,文件输出的相关设置
log4j.appender.FILE=org.apache.log4j.RollingFileAppender
log4j.appender.FILE.File=D:/logs/log4j.log
log4j.appender.FILE.MaxFileSize=5MB
log4j.appender.FILE.Threshold=DEBUG
log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH\:mm\:ss} [%t] %-5p %c(line-%L) %-4r %x - %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为日志实现
1
2
3
4<settings>
<!--标准日志工厂实现-->
<setting name="logImpl" value="LOG4J"/>
</settings>log4j的使用!,直接测试运行查询的结果
![image-20210306142348171](MyBatis.assets\image-20210306142348171.webp)
简单使用
在要使用log4j的类中,导入包 :import org.apache.log4j.*;
日志对象,参数为当前类class
1
2
3public class Log4jTest {
static Logger logger = Logger.getLogger(Log4jTest.class);
}日志级别
1
2
3
4static Logger logger = Logger.getLogger(Log4jTest.class);
logger.info("info:加入了testLog");
logger.debug("info:加入了DEBUG");
logger.error("info:加入了error");
七、分页
为什么要分页?
- 减少数据的处理量
- 减少数据的处理
1、使用Limit分页
LIMIT
一个参数( LIMIT 8 ):从第0跳数据开始查,查询8条数据
两个参数( LIMIT 1 , 5 ):从第1条数据开始查询,每次查询五条数据
1
2SELECT *FROM user LIMIT startIndex,pageSize
SELECT *FROM user LIMIT 1,3
使用MyBatis实现分页,核心SQL
接口:
1
2
3
4/**
* 分页查询用户
*/
List<User> getUserByLimit(Map<String,Integer> map);Mapper.xml
1
2
3
4
5<select id="getUserByLimit" parameterType="map" resultType="userTest">
select *
from mybatis.user
limit #{startIndex} , #{pageSize}
</select>
测试
1
2
3
4
5
6
7
8
9
10
11
12
13public class UserDaoImpl {
public void testUserDao() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String, Integer> map = new HashMap<>();
map.put("startIndex" , 0);
map.put("pageSize" , 2);
mapper.getUserByLimit(map).forEach(System.out::println);
sqlSession.close();
}
}
2、RowBounds分页(不建议使用)
不再使用SQL分页实现
接口
1
2
3
4
5/**
* 查询所有用户
* @return 返回所有用户,将得到的结果集通过Java代码进行分页处理
*/
List<User> getUserByRowBounds();Mapper.xml
1
2
3
4<select id="getUserByRowBounds" resultType="userTest">
select *
from mybatis.user
</select>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void testRowBounds() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
//通过RowBounds实现(不推荐使用,了解下)
RowBounds rowBounds = new RowBounds(0 , 2);
//通过Java代码层面实现分页
//com.hngy.dao.user.UserMapper.getUserByRowBounds(下面方法第一个参数的值的写法)
List<User> users = sqlSession.selectList("类的全路径和具体的方法" , null , rowBounds);
users.forEach(System.out::println);
sqlSession.close();
}
3、分页插件
导入
1
2
3
4
5
6<!-- pageHelper分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>使用:(这里列举了两种使用方式)
PageHelper.offsetPage(参数1,参数2 )==(参数一:从第几条数据开始查询,参数二:每页查询多少条数据)==
接口
1
2
List<User> selectAll();
- 测试
1
2
3
4
5
6
7
8
9
10
11
public void test1() {
//参数一:从第几条数据查询 ----- 参数二:每页显示的数量
//执行的SQL:select *from user LIMIT ?, ?
Page<User> page = PageHelper.offsetPage(2, 4);
userMapper.selectAll();
System.out.println(page);
}
- PageHelper.startPage(参数一, 参数二);==**(参数一:从第几页开始查询。参数二:每页显示多少条数据)**==
> **这里只介绍了两种方法的使用,其他方法的使用可以去PageHelper官网查询使用**
八、使用注解开发
- 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
1、面向接口编程概念
- 之前都学过面向对象编程,也学过接口,但在真正的开发中,很多时候都会选择面向接口编程
- 根本原因:解耦,可拓展,提高复用,分层开发中,上层不管使用具体的实现,大家都遵守的标准,使得开发变得容易,规范性好
- 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
- 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。
- 关于接口的理解
- 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。
- 接口的本身反映了系统设计人员对系统的抽象理解。
- 接口应有两类:第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
- 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
- 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。
2、使用注解开发
- 注解开发可以和XML配置一起使用,而不是使用了注解就不能使用XML了,两者可以一起使用开发,对于一些简单的sql语句可以使用注解开发,对于一些复杂的sql语句就使用XML文件来实现方便一点了
- 在MyBatis中还是建议使用XML配置文件来实现,因为大量使用注解的话,会使代码变得很乱,很难维护,” 注解一时爽,维护火葬场 “
1、注解在接口上,当出现大量数据库和JavaBean的字段不对应的时候,注解开发就会变得不好,所以可以配置文件和注解一起使用(当出现方法中需要传入参数的时候,需要加上@param(“变量名”) 注解,加上这个注解后,sql语句中的需要用到的参数的名字会和注解中定义的变量名来匹配,如果sql语句中通配符中的变量名和注解中填写的参数的名字不一样的话,会报错)
1
2
3
4
5
6
7/**
* 查询全部用户
*
* @return 返回所有用户的信息
*/
List<User> getUserById(int id);
需要在核心配置文件中绑定接口
1
2
3
4<!--使用注解开发需要绑定接口-->
<mappers>
<mapper class="com.hngy.dao.user.UserMapper"/>
</mappers>
测试
1
2
3
4
5
6
7
8
9
public void testGetUsers() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.getUserById(1).forEach(System.out::println);
sqlSession.close();
}
本质:反射机制实现
底层:动态代理
MyBatis详细的执行流程!
3、CRUD
当接口中有多个参数的时候需要使用@param注解,sql语句中占位符添加的参数对应@param注解中传入的值
我们可以在工具类创建的时候自动提交事务(不建议开启自动提交,因为不能进行事务)
在如下的代码的getSqlSession()方法返回的方法中传入 “ true ”参数就会自动提交事务了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public class MyBatisUtil {
private static SqlSessionFactory sqlSessionFactory;
static {
InputStream inputStream = null;
try {
//使用mybatis第一步,sqlSessionFactory对象
// String resource = "org/mybatis/example/mybatis-config.xml";
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
assert inputStream != null;
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
//SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true);
}
}
Update
编写接口:
1
2
3
4
5
6
7
8/**
* 修改用户信息
*
* @param user 需要修改的用户的信息
* @return 返回修改的行数
*/
int updateUser(User user);
- 测试:
1
2
3
4
5
6
7
8
9
10
11
12
public void testUpdate() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.updateUser(new User(5 , "老五" , "123"));
sqlSession.commit();
sqlSession.close();
}
Delete:
编写接口:
1
2
3
4
5
6
7
8
9/**
* 根据用户id和用户的名字删除用户
*
* @param id 用户id
* @param name 用户的名字
* @return 返回修改的行数
*/
int deleteUser(int id , String name);测试:
1
2
3
4
5
6
7
8
9
10
11
12
public void testDelete() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
System.out.println(mapper.deleteUser(5 , "玛尔扎哈"));
sqlSession.commit();
sqlSession.close();
}
Insert
编写接口:
1
2
3
4
5
6
7
8/**
* 添加用户
*
* @param user 用户的信息
* @return 返回修改的行数
*/
int insertUser(User user);
- 测试
1
2
3
4
5
6
7
8
9
10
11
12
public void testInsert() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.insertUser(new User(5 , "玛尔扎哈" , "5"));
sqlSession.commit();
sqlSession.close();
}
- 注意:必须要将接口注册在核心配置类中
关于@param注解
- 基本参数类型的参数或者string类型,需要加上
- 引用类型不需要加(Java类)
- 如果只有一个基本类型的话,可以忽略,但是建议加上
- 我们在SQL中引用的就是我们这里的@param()中设定的属性名
九、Lombok(不建议使用,但是方便)
没有强制要求使用最好不使用,具体使用去网上找,不推荐使用,了解
lombok官网:https://projectlombok.org/
1
2Project 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.
Lombok 项目是一个 java 库,可自动插入编辑器并构建工具,将您的 java 弹出。永远不要再写另一个获取器或等于方法,用一个注释您的类有一个功能齐全的建设者,自动化您的记录变量,等等。
- Java library、plus、build tools、with one annotation your class
使用步骤:
在IDAE中安装Lombok插件
在项目中导入lombok的jar包
1
2
3
4
5
6
7<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>在实体类上加注解即可:
- @Data:生成无参构造、get、set、toString、hashcode、equals
- @AllArgsConstructor:生成全参构造函数
@Setter 和 @Getter:
- 放在类上:生成全部属性的set和set方法
- 放在属性上:生成对应属性的set方法和set方法
1
2
3
4
5
6and
and
十、多对一
场景:在电商项目中的多个商品对应一个类别的时候的查询需要使用到多对一,此时是多个商品属于同一种类别
多对一
- 例如:多个学生对应一个老师,这就是一种多对一的关系
- 对于学生而言:多个学生关联一个老师【多对一】
- 对于老师而言:集合,一个老师有很多学生【一对多】
创建表:
1
2
3
4
5
6
7CREATE TABLE `teacher`
(
`id` INT(10) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = INNODB
DEFAULT charset = utf8;1
2
3
4
5
6
7
8CREATE TABLE `student` (
`id` INT ( 10 ) NOT NULL,
`name` VARCHAR ( 30 ) DEFAULT NULL,
`tid` INT ( 10 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `fktid` ( `id` ),
CONSTRAINT `fktid` FOREIGN KEY ( `tid` ) REFERENCES `teacher` ( `id` )
) ENGINE = INNODB DEFAULT charset = utf8
1、测试环境搭建
导入lombok
1
2
3
4
5
6
7<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
建立实体类Teacher,Student
1
2
3
4
5
6
7
8
public class Student {
private int id;
private String name;
//学生需要关联一个老师
private Teacher teacher;
}1
2
3
4
5
public class Teacher {
private int id;
private String name;
}
建立Mapper接口
1
2public interface StudentMapper {
}1
2public interface TeacherMapper {
}
- 建立Mapper.xml文件
- 在核心配置文件中绑定Mpper接口或者文件【多多种方式注册】
- 测试是否能够执行成功
2、按照查询嵌套处理(不推荐)
和解决属性名和字段名不一致的问题的方式差不多
复杂的属性,需要单独处理:子查询(resultMap中标签的使用)
对象:association
集合:collection
对于在association和collection中的ofType和javaType的区别
- 当定义了一个属性为:List
list - 在使用association标签的时候
- ofType:此时没有ofType这个属性
- javaType:此时的JavaType为Student,javaType属性中的值为集合中泛型的值,如果没有写类型为
- 在使用collection标签的时候
- ofType:此时的属性的值为Student,是集合中的泛型的类型,如果集合中没有给定类型,那么类型为Object
- javaType:此时的属性的值为ArrayList,填写的是集合的类型,而不是集合中泛型的类型
- 当定义了一个属性为:List
1 | <!-- ==========================按照查询嵌套处理================================================= --> |
3、按照结果嵌套处理(推荐)
1 | <select id="getStudent2" resultMap="StudentTeacher2"> |
十一、一对多
场景:在电商项目中的用户查询订单信息的时候就要使用到一对多(一个用户对应多个订单信息)
能使用按照查询结果嵌套处理结果,就尽量不使用按照查询嵌套处理
比如一个老师拥有多个学生
- 对于老师而言就是一对多的关系
1、环境搭建
实体类
1
2
3
4
5
6
public class Student {
private int id;
private String name;
private int tid;
}1
2
3
4
5
6
7
8
public class Teacher {
private int id;
private String name;
//一个老师拥有多个学生
private List<Student> students;
}
2、按照结果嵌套处理(推荐)
- 一对多对于结果的嵌套处理需要在resultMap标签中使用collection标签来进行嵌套处理,而不是多对一中在resultMap标签中使用association标签来进行嵌套处理。且association标签中的javaType需要改为ofType(两者皆为指定参数的类型)
- 对于在association和collection中的ofType和javaType的区别
- 当定义了一个属性为:List
list - 在使用association标签的时候
- ofType:此时没有ofType这个属性
- javaType:此时的JavaType为Student,javaType属性中的值为集合中泛型的值,如果没有写类型为
- 在使用collection标签的时候
- ofType:此时的属性的值为Student,是集合中的泛型的类型,如果集合中没有给定类型,那么类型为Object
- javaType:此时的属性的值为ArrayList,填写的是集合的类型,而不是集合中泛型的类型
- 当定义了一个属性为:List
1 | <!--按照结果嵌套查询--> |
3、按照查询嵌套处理(不推荐)
1 | <!--========================按照查询嵌套处理======================================--> |
4、小结
- 关联 - association 【多对一】
- 集合 - collection 【一对多】
javaType & ofType 有什么区别
- javaType是用来指定实体类中属性的类型
- ofType:用来指定映射到List或者集合中的 pojo类型,就是泛型中的约束类型
- 例如:当一个集合为 List
:那么此时的
- 例如:当一个集合为 List
注意:
- 保证SQL的可读性,尽量通俗易懂
- 注意一对多和多对一中,属性名和字段名的问题
- 如果问题不好排查错误,可以使用日志,建议使用Log4j
- 慢SQL :写SQL一秒 查询出来结果需要使用1000秒,不要写慢SQL
5、面试高频
MySQL引擎
InnoDB底层原理
索引
索引优化
十二、动态SQL
官网地址:https://mybatis.org/mybatis-3/zh/dynamic-sql.html
什么是动态SQL:动态SQL就是指根据不同的条件生成不同的条件生成不同的SQL语句,实现SQL的复用
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。
在 MyBatis 之前的版本中,需要花时间了解大量的元素。
借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
对于动态SQL中的一些判断:(这些只能再标签中使用,比如在if等标签外使用的话汇报错误)
1
2
3
4
5
6
7gt 对应 >
gte 对应 >=
lt 对应 <(会报错 相关联的 "test" 属性值不能包含 '<' 字符)
lte 对应 <=(会报错 相关联的 "test" 属性值不能包含 '<' 字符)
1、搭建环境
创建表
1
2
3
4
5
6
7CREATE 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 '浏览量'
) ENGINE = INNODB DEFAULT charset = utf8
2、IF和WHERE
IF有时需要和WHERE标签一起使用,例如如下代码中需要使用if标签来判断时要添加where,如果没有where标签就会报错,where标签表示,其中至少有一个条件满足的时候就会在SQL语句中添加where,要是where中的if标签没有一个满足条件的话就不会在sql语句中添加where,这样可以避免报错,要是只有一个条件满足条件,那么if标签中的and就不会添加,所以,两者需要一起配合使用,不配合一起使用的话会出现容易出现错误
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
创建一个基础工程
导包
1
2
3
4
5
6
7<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>编写配置文件
编写实体类
1
2
3
4
5
6
7
8
9
public class Blog {
private String id;
private String title;
private String author;
private Date creatTime;
private int views;
}接口
1
2
3
4
5
6
7
8public interface BlogMapper {
/**
* 查询博客
*
* @return 返回博客的信息
*/
List<Blog> queryBlog(Map map);
}编写实体类对于的Mapper接口和对应的Mapper.xm文件
1
2
3
4
5
6
7
8
9
10
11
12
13<!--if-->
<select id="queryBlog" parameterType="map" resultType="blog">
select *from mybatis.blog
<where>
<if test="title != null">
and title=#{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
3、choose、when、otherwise
- 需要在最外层添加where标签
- 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
- 当使用了多个when标签的时候,运行方式是从上到下执行的,如果前面的when标签中的条件满足了,那么后面的标签中的东西就不会运行了,就和switch相似,只会运行一个case条件下的语句。不同的是switch不能写条件一样的,而这里多个when标签可以写多个条件一样的,但从上到下,第一个满足后,后面的就不会再执行了
1 | <!--choose--> |
4、trim、where、set
Where
1
2
3
4
5
6
7
8
9
10
11
12
13<!--if-->
<select id="queryBlog" parameterType="map" resultType="blog">
select *from mybatis.blog
<where>
<if test="title != null">
and title=#{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
set
1
2
3
4
5
6
7
8
9
10
11
12
13<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title=#{title},
</if>
<if test="author != null">
author=#{author},
</if>
</set>
where id=#{id}
</update>
- trim:具体使用去官网看常用的mybatis已经写好了,不需要自己写了
所谓的动态SQL,本质还是SQL语句,只是我们可以再SQL层面,去执行一个逻辑代码
5、SQL片段
有时候,我们可能会将一些一样的代码抽取出来,方便复用
使用SQL标签抽取公共部分
1
2
3
4
5
6
7
8
9
10<!--SQL片段,复用SQL字段-->
<sql id="if-title-author">
<if test="title != null">
title=#{title},
</if>
<if test="author != null">
author=#{author},
</if>
</sql>
再需要使用的地方使用include标签引用即可
1
2
3
4
5
6
7
8<!--更新数据库表-->
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<include refid="if-title-author"/>
</set>
where id=#{id}
</update>注意事项:
- 最好基于单表来定义SQL片段
- 不要存在Where标签
6、Foreach
1 | <!--将如下代码使用foreach查询--> |
动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了
建议:
- 先在MySQL中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可
批量操作
在MyBatis中还能进行批量操作。如果执行相同的SQL语句,使用批量操作可以让MyBatis只执行一次SQL语句,其他只插入值,大大减少了运行时间
十三、缓存
1、简介
1 | 查询:连接数据库 ,耗资源 |
- 什么是缓存
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高效率,解决了高并发系统的性能问题
- 为什么要使用缓存
- 减少和数据库的交互次数,减少系统开销,提高系统效率
- 什么样的数据能使用缓存
- 经常查询并且不经常改变的数据【可以使用缓存的】
- 什么样不能使用缓存
2、MyBatis缓存
- MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地指定和配置缓存,缓存可以极大的提高查询效率
- MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession缓存的级别,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
- 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存
3、一级缓存
- 一级缓存也叫本地缓存:
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要相同的数据,直接从缓存中拿,没必要再去查询数据库
测试步骤:(可以看在一个SqlSession中到查询相同的数据时候只会执行一次sql语句,第二次只会从缓存中获取)
开启日志
1
2
3
4<settings>
<!--标准日志工厂实现-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>测试再一个SqlSession中查询两次相同的记录
1
2
3
4
5
6
7
8
9
10
11
12
public void queryUser() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//同时执行两次相同的查询
mapper.queryUser(1).forEach(System.out::println);
mapper.queryUser(1).forEach(System.out::println);
sqlSession.close();
}查看日志输出:看一下结果可以看到,sql语句只执行了一次,最后获取的地址在最后两行,此时的地址完全一样,第二次是在缓存中获取的
1
2
3
4
5
6
7
8
9==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, 韩信, 1
<== Total: 1
User(id=1, name=韩信, psd=1)
User(id=1, name=韩信, psd=1)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@266374ef]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@266374ef]
当查询同一条语句执行不同的结果的时候:可以看sql语句执行了两次,
当查询操作执行完后执行修改操作再执行查询操作的结果为:此时查询操作相同却不再查询缓存了,不管修改的是否是查询的数据,此后的查询操作都会查询查询,而不是查询缓存
![image-20210307192746569](MyBatis.assets\image-20210307192746569.webp)
缓存失效的情况:
查询不同的东西
增删改操作,可能会改变原来的数据,所以必定会刷新缓存
查询不同的Mapper.xml文件
手动清理缓存(执行SqlSession类中的)
1
2SqlSession sqlSession = MyBatisUtil.getSqlSession();
sqlSession.clearCache();//执行clearCache方法清理缓存
- 一级缓存是默认开启的,只在一个SqlSession中有效,也就是拿到连接到关闭的这个区间段
- 一级缓存相当于一个Map
4、二级缓存
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
基于namespace级别的缓存,一个名称空间,对应一个二级缓存
工作机制
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应一级缓存就没了,但是我们想要的是,会话关闭了。一级缓存中的数据被保存到二级缓存中;
- 新的会话查询信息,就可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
步骤:
开启全局缓存(在mybatis-config.xml文件中开启全局缓存)
1
2<!--显示开启全局缓存-->
<setting name="cacheEnabled" value="true"/>在要使用二级缓存的Mapper中开启:
1
2<!--在当前Mapper.xml中使用二级缓存-->
<cache/>也可以自定义一些参数:参数的具体意思可以去官网查询:
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
1
2<!--在当前Mapper.xml中使用二级缓存-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>测试:根据查询结果:可以看到开启了二级缓存之后不会再进行二次查询数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void queryUser() {
SqlSession sqlSession = MyBatisUtil.getSqlSession();
SqlSession sqlSession2 = MyBatisUtil.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
mapper.queryUser(1).forEach(System.out::println);
sqlSession.close();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
mapper2.queryUser(1).forEach(System.out::println);
sqlSession2.close();
/*
查询结果:可以看到开启了二级缓存之后不会再进行二次查询数据库
==> Preparing: select * from user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, password
<== Row: 1, 韩信, 1
<== Total: 1
User(id=1, name=韩信, psd=1)
Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1787bc24]
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1787bc24]
Returned connection 394771492 to pool.
Cache Hit Ratio [com.hngy.dao.UserMapper]: 0.5
User(id=1, name=韩信, psd=1)
*/
}
- 问题一:我们需要将实体类序列化!否则会报错:(在实体类后实现Serializable接口就行了)
1
Cause by:java.io.NotSerializableException:
- 小结:
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有数据都会先放在一级缓存中;
- 只有当会话提交,或者关闭的时候,才会提交到二级缓存中
5、MyBatis缓存原理:
- 具体理解上网找
6、自定义缓存-ehcache(了解)
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。
后面使用Redis来做缓存! K—V(MongoDB)
要在使用过程中先导包
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>在Mapper中指定使用ehcache缓存实现:
1
2
3<!-- 在Mapper.xml文件中需要导入ehcache缓存 -->
<!--在当前Mapper.xml中使用二级缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
使用ehcache需要编写xml配置文件:ehcaher.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home – 用户主目录
user.dir – 用户当前工作目录
java.io.tmpdir – 默认临时文件路径
-->
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<!--
name:缓存名称。
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数。
eternal:对象是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:是否保存到磁盘,当系统当机时
timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
clearOnFlush:内存数量最大时是否清除。
memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<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>
后面使用Redis来做缓存! K—V(MongoDB)
十四、使用MyBatis的结论
1、在使用MyBatis中遇到的异常
当执行查询语句的时候返回了多条结果,实际方法只是返回一条语句:
如下方法只返回一个实体类,结果查询结果为多条数据时候报错问题
1
2
3
4
5
6
7
8
9
10
11
12
13User queryUser(int id);
/*执行结果:此时返回的结果有两条
<== Row: 1, 李白, 1, 1, 李白, 0, 男, 18370707, 2174809@qq.com
<== Row: 1, 李白, 1, 1, 李白, 0, 男, 18370707, 2174809@qq.com
<== Total: 2
*/
/*
异常信息:这个异常信息是由于方法只能返回一条语句,结果却返回了多条语句,所以不知道返回哪一条语句
org.apache.ibatis.exceptions.TooManyResultsException:
Expected one result (or null) to be returned by selectOne(), but found: 2
*/
2、在使用MyBatis时遇到的问题
在使用多对一的时候遇到的问题:
1
在使用多对一的时候需要将结果集全部映射,不然会出现输出为空的情况,就算此时实体类中的字段和数据库中的字段还有开启了MyBatis的驼峰命名法也没有用,必须要全部映射
在使用一对多的时候遇到的问题:
-
1
2
3
4
5
6
7
8
9public interface UserMapper {
/**
* 根据用户id,获取一个用户下面的所有信息,包括购物车信息,订单信息
*
* @param id 用户id
* @return 返回该用户的所有信息
*/
User queryUser(int id);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class User {
private int id;
private String name;
//每个用户有多个购物车项;这里是一对多的关系,一个用户有多个购物车信息
private List<ShopCart> shopCarts;
}
public class ShopCart {
private String bookName;
private Integer id;
}
-
使用resultMap来映射需要将所有字段映射,不然会出现异常,需要向下面的XML文件中的resultMap标签中一样,给需要查询的类一样映射,**(现在还只是在一对多的时候碰到,以后再碰到其他情况的时候再来补充)**
- 异常1:输出的值为空,不能将查询出来的值保存到对应的实体类中,
- 异常2:因为此时是一对多,且类中是一个类中有其他类的集合,此时如果不把字段一一映射的话,有时候由于SQL语句查询的是多条语句且没有使用resultMap标签中的collection标签给类中的集合赋值,此时会系统会以为要返回的是多条数据,但是由于方法的返回值只能返回一个值,所以不给属性一一映射的话可能会报如下错误:
1
2
3
4
5
/*
异常信息:这个异常信息是由于方法只能返回一条语句,结果却返回了多条语句,所以不知道返回哪一条语句
org.apache.ibatis.exceptions.TooManyResultsException:
Expected one result (or null) to be returned by selectOne(), but found: 2
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<select id="queryUser" resultMap="queryUser">
SELECT u.*,
s.id sid,
s.book_name,
FROM mybatis.s_user u,
mybatis.s_shopcart s
WHERE u.id = s.user_id
AND u.id = #{id};
</select>
<resultMap id="queryUser" type="user">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="shopCarts" ofType="shopCart">
<result property="id" column="sid"/>
<result property="bookName" column="book_name"/>
</collection>
</resultMap>
在MyBatis中不能使用方法重载
如下定义方法重载在编译的时候虽然不会报错,但是在运行的时候会报错误:因为方法名字重复
MyBatis的标签中id的底层是使用了:【 id=Mapper的全类名+’.’+方法名 】的方式定义的,所以不能方法重载
1
2
3
4
5public interface InterfaceTest{
List<Book> queryBook(Map map);
List<Book> queryBook();
}
使用MyBatis的时候,实体类一定要有无参构造,没有无参构造运行时候会出现异常
Mybatis框架会调用这个默认构造方法来构造实例对象,即实体类需要通过Mybatis进行动态反射生成。
1
java.lang.IllegalArgumentException: argument type mismatch//类型匹配异常
模糊查询:
一般模糊查询语句如下:
1
SELECT 字段 FROM 表 WHERE 某字段 Like 条件
其中关于条件SQL提供了四种匹配模式:
%:表示任意0个或多个字符。可匹配任意类型和长度的字符,有些情况下若是中文,请使用两个百分号(%%)表示。
1
2
3
4
5
6
7
8比如 SELECT * FROM [user] WHERE u_name LIKE '%三%'
将会把u_name为“张三”,“张猫三”、“三脚猫”,“唐三藏”等等有“三”的记录全找出来。
另外,如果需要找出u_name中既有“三”又有“猫”的记录,请使用and条件
SELECT * FROM [user] WHERE u_name LIKE '%三%' AND u_name LIKE '%猫%'
若使用 SELECT * FROM [user] WHERE u_name LIKE '%三%猫%'
虽然能搜索出“三脚猫”,但不能搜索出符合条件的“张猫三”。_ : 表示任意单个字符。匹配单个任意字符,它常用来限制表达式的字符长度语句:
1
2
3
4
5比如 SELECT * FROM [user] WHERE u_name LIKE '_三_'
只找出“唐三藏”这样u_name为三个字且中间一个字是“三”的;
再比如 SELECT * FROM [user] WHERE u_name LIKE '三__';
只找出“三脚猫”这样name为三个字且第一个字是“三”的;[ ]:表示括号内所列字符中的一个(类似正则表达式)。指定一个字符、字符串或范围,要求所匹配对象为它们中的任一个
1
2
3
4
5
6比如 SELECT * FROM [user] WHERE u_name LIKE '[张李王]三'
将找出“张三”、“李三”、“王三”(而不是“张李王三”);
如 [ ] 内有一系列字符(01234、abcde之类的)则可略写为“0-4”、“a-e”
SELECT * FROM [user] WHERE u_name LIKE '老[1-9]'
将找出“老1”、“老2”、……、“老9”;:表示不在括号所列之内的单个字符。其取值和 [] 相同,但它要求所匹配对象为指定字符以外的任一个字符。
1
2
3
4
5比如 SELECT * FROM [user] WHERE u_name LIKE '[^张李王]三'
将找出不姓“张”、“李”、“王”的“赵三”、“孙三”等;
SELECT * FROM [user] WHERE u_name LIKE '老[^1-4]';
将排除“老1”到“老4”,寻找“老5”、“老6”、……