Mybatis学习笔记
Mybatis框架
Mybatis 是一个使用java编写的持久层框架。它封装了 JDBC的很多细节 ,使开发者只需要关注 sql 语句,而无需关注注册驱动、创建连接、创建 Statement 等繁杂的过程。
采用了 ORM 思想 实现了结果集的封装
> ORM(Object Relational Mapping)对象关系映射。简单地说,就是把数据库表和实体类及实体类的属性对应起来,让我们可以通过操作实体类来操作数据库表。
注意事项
- 在 Mybatis 中,持久层的操作接口名称和映射文件也叫 Mapper ,所以 UserMapper 和 IUserDao 是一样的
- 映射配置文件的 mapper 标签 namespace 属性的取值必须是 mapper 接口的全限定类名 - 映射配置文件的操作配置,id 属性的取值必须是 mapper 接口的方法名
第三个: mybatis的映射配置文件位置必须和dao接口的包结构相同 第四个: 映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名 第五个: 映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
当我们遵从了第三,四,五点之后,我们在开发中就无须再写dao的实现类。
第一步: 读取配置文件 第二步: 创建SqlSessionFactory工厂 第三步: 创建SqlSession 第四步: 创建Dao接口的代理对象 第五步: 执行dao中的方法 第六步: 释放资源
注意事项: 不要忘记在映射配置中告知mybatis要封装到哪个实体类中 配置的方式:指定实体类的全限定类名
mybatis基于注解的入门案例: 把IUserDao.xml移除,在dao接口的方法上使用@Select注解,并且指定SQL语句 同时需要在SqlMapConfig.xml中的mapper配置时,使用class属性指定dao接口的全限定类名。 明确: 我们在实际开发中,都是越简便越好,所以都是采用不写dao实现类的方式。 不管使用XML还是注解配置。
但是Mybatis它是支持写dao实现类的。
``
1 |
|
解释
- 1.读配置文件不用虚拟路径和绝对路径(容易出现问题),而用①类加载器读取类路径的配置文件。②使用ServletContext对象的getRealPath()
- 2.创建工厂mybatis使用了构建者模式。
- 构建者模式:把对象的创建细节隐藏,使使用者直接调用方法即可拿到对象。
- 3.生产SqlSession使用了工厂模式。
- 优势:解耦,降低类之间的依赖关系
- 4.创建Dao接口实现了代理模式。
- 优势:不修改源码的基础上对功能增强。
自定义Mybatis分析
mybatis在使用代理dao的方式实现增删改查时做什么事呢? 只有两件事: 第一:创建代理对象 第二:在代理对象中调用selectList
)
添加操作,查询操作(CRUD)
1 |
|
注意:
- @Before 在测试方法执行前执行 @After :在测试方法后执行
- @Test 声明为测试方法,可以单独执行
- session.commit(); 提交事务,提交后“保存”方法才会生效。
1 |
|
选择键值
OGNL表达式(Object Graphic Navigatation Language)对象 图 导航 语言
- 它是通过对象的取值方法来获取数据,在写法上把get省略了
- 比如获取用户名称:类中写法:user.getUsername(); OGNL写法:user.username.
使用pojo包装类进行查询
- 在开发中如果想实现复杂查询 ,查询条件不仅包括用户查询条件,还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用 pojo 包装对象传递输入参数
- 编写
QueryVo
类来封装查询条件

①QueryVo封装了User
1 |
|
②配置mapper.xml
1 |
|
注意:
- ```mysql parameterType="com.fyw.domain.QueryVo",
resultType="com.fyw.domain.User">
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- #{user.username} 用ognl实现,非username
③写测试方法
```java
@Test
public void testFindByVo(){
QueryVo vo = new QueryVo();
User user = new User(); //创建对象
vo.setUser(user); //QueryVo封装User
user.setUsername("%王%"); //模糊查询字段
List<User> users = userDao.findUserByVo(vo); //封装后的方法
for (User u:users ) {
System.out.println(u);
}
}
解决实体类属性名称和数据库列名不对应的方法(自定义类中:userId;数据库中列名:Id)
第一种方式:起别名
- 在sql语句中用as 起别名从而实现对应(在mysql层面效率高)
第二种方式:配置resultMap
1 |
|
注意:
- property:java中的名称 column:mysql中的名称
- 语句上不用resultType 而用 resultMap
配置文件讲解
properties标签
配置数据库连接的时候,我们可以采用以下几种方式来配置:
- 第一种,采用全局的内部配置。采用这种方式的话,如果需要配置多个数据库环境,那么像
username、password
等属性就可以复用,提高开发效率。
1 |
|
注意:properties配置后 dataSource的value需要用${driver}代替!
第二种,使用
resources
属性引入外部配置文件(常用)编写配置文件
jdbcConfig.properties
。配置文件名没有限制,但是配置文件一定要放在类路径下
1 |
|
1 |
|
1 |
|
注意:有前缀jdbc. 设置dataSource时value需要加前缀。
- 第三种,使用
url
属性引入外部配置文件
该方法的外部文件可以放在任意位置,但是路径写法必须按照 Url 的方式,写起来比较麻烦,推荐第二种方法
1 |
|
- URL:Uniform Resouce LOcator,即统一资源定位符。它可以唯一标识一个资源的位置,由四部分组成:协议、主机、端口、路径。
- 例如:http://localhost:8080/mybatisserver/demo1,其中
http
为协议,localhost
为主机,8080
为端口号,/mybatisserver/demo1
为uri(路径)- URI:Uniform Resource Identifier,即统一资源标识符。它是在应用中可以唯一定位一个资源的。
typeAliases
标签
- 之前在编写映射文件的时候,
resultType
这个属性可以写int、INT
等,就是因为 Mybatis 给这些类型起了别名。Mybatis 内置的别名如表格所示:
别名 | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
如果我们也想给某个实体类指定别名的时候,就可以采用
typeAliases
标签。
1 |
|
type 是属性指定的实体类全限定类名 alias属性指定别名
- 当我们有多个实体类需要起别名的时候,那么我们就可以使用
package
标签。
1 |
|
package
标签指定要配置别名的包,当指定之后,该包下的所有实体类都会注册别名,并且别名就是类名,不再区分大小写
package
标签还可以将某个包内的映射器接口实现全部注册为映射器,如下所示
1 |
|
这样配置后,我们就无需一个一个地配置 Mapper 接口了。不过,这种配置方式的前提是映射配置文件位置必须和dao接口的包结构相同,如图所示
3.动态sql语句
MyBatis 的强大特性之一便是它的动态 SQL。如果你有使用 JDBC 或其它类似框架的经验,你就能体会到根据不同条件拼接 SQL 语句的痛苦。例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL 这一特性可以彻底摆脱这种痛苦。
if标签的使用
1 |
|
注意
WHERE 1 =1
是防止所有条件都为空时拼接 SQL 语句出错。因为不加上1 = 1
这个恒等条件的话,如果后面查询条件都没拼接成功,那么 SQL 语句最后会带有一个WHERE
关键字而没有条件,不符合 SQL 语法。- `
标签中
test` 属性是必须的,表示判断的条件。其中有几点需要注意:- 如果
test
有多个条件,那么必须使用and
进行连接,而不能使用 Java 中的&&
运算符。 test
中的参数名称必须与实体类的属性保持一致,也就是和#{参数符号}
保持一致。- 如果判断条件为字符串,那么除了判断是否为
null
外,最好也判断一下是否为空字符串,''
,防止 SQL语句将其作为条件查询。
- 如果
WHERE标签
将if语句放在where中,可以不用写where 1=1 ,从而优化语句
标签只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入 `WHERE` 子句。而且,若语句的开头为 `AND` 或 `OR`,
标签也会将它们去除。- 就是该标签可以动态添加
WHERE
关键字,并且剔除掉 SQL 语句中多余的AND
或者OR
。
foreach 标签的使用
- 如果使用普通 SQL
语句的话,那么查询语句应该这样写:
SELECT \* FROM user WHERE id IN(41,42,43);
- 因此,如果想使用动态 SQL
来完成的话,那么我们就应该考虑如何拼接上
id IN(41,42,43)
这一串内容,这时候,我们的<foreach></foreach>
标签就出场了。
流程:
首先修改
QueryVo
类,增加一个成员变量用于存放 id 集合,并增加其getter()/setter()
接下来添加接口方法,并配置映射文件
1 |
|
注意
collection
: 代表要遍历的集合或数组,这个属性是必须的。如果是遍历数组,那么该值只能为array
open
: 代表语句的开始部份。close
: 代表语句的结束部份。item
: 代表遍历集合时的每个元素,相当于一个临时变量。separator
: 代表拼接每个元素之间的分隔符。- 注意,SQL 语句中的参数符号
#{id}
应该与item="id"
保持一致,也就是说,item
属性如果把临时变量声明为uid
的话,那么使用时就必须写成#{uid}
。
4.Mybatis 多表查询
建立实体类关系
在Account中生成getUser方法,建立主表和从表关系
1
2
3
4
5
6public User getUser(){
return user;
};
public Integer getId() {
return id;
}设置xml映射文件
1
2
3
4
5
6
7
8
9
10
11
12
13<resultMap id="accountUserMap" type="com.fyw.domain.Account">
<id property="id" column="aid"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!-- 一对一关系映射,配置user-->
<association property="user" column="uid" javaType="com.fyw.domain.User">
<id property="id" column="aid"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</association>
</resultMap>- 注意:
用于一对一映射的,其中property表示要关联的属性,javaType表示要关联实体类的全限定类名(Account关联User) - 因为 SQL 语句中为
account
表的id
字段起了别名aid
,所以在定义 resultMap 的时候,记得主字段写column="aid"
,而不是column="id"
。
- 注意:
然后写sql语句
1
2
3<select id="findAll" resultMap="accountUserMap">
SELECT U.*, a.id AS aid, a.uid, a.money from account a, user u WHERE a.uid = u.id;
</select>最后写测试方法
1
2
3
4
5
6
7@Test //测试方法,可以单独运行方法
public void testFindAll() {
List<Account> accounts = accountDao.findAll();
for (Account account: accounts) {
System.out.println(account);
System.out.println(" + " + account.getUser());
}结果截图
4.延迟加载与立即加载
- 延迟加载
- 延迟加载就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据,延迟加载也称懒加载。
- 在一对多或多对多的表关系中,通常情况下我们都是采用延迟加载。
- 立即加载
- 立即加载就是不管是否需要数据,只要一进行查询,就会把相关联的数据一并查询出来。
- 在多对一或一对一的表关系中,通常情况下我们都是采用立即加载。
4.1 一对一实现延迟加载
1 |
|
- 注意,在编写 Mybatis 的配置文件时,文档结构一定不可以随便写,一定要按照官方文档所要求的顺序,比如说:
标签不可以写在
下方。具体文档结构见下图:
- 修改上一篇笔记编写好的账户映射文件
AccountMapper.xml
1 |
|
- `
标签中的
select属性表示我们**要调用的映射语句的 ID**,它会**从
column` 属性指定的列中检索数据,作为参数传递给目标 select 语句**。 column
属性指定传递给我们要调用的映射语句的参数。
缓存
- 什么是缓存?存在内存中的数据
- 为什么使用缓存?减少和数据库的交互次数,提高执行效率
- 什么样的数据使用缓存? 经常查询且不常改变的;数据的正确与否对结果影响不大的
一级缓存
- 一级缓存是 SqlSession 级别的缓存,只要 SqlSession 没有 flush 或 close,它就会存在。当调用 SqlSession 的修改、添加、删除、commit()、close()、clearCache() 等方法时,就会清空一级缓存。SqlSession不存在缓存也就不存在了。
一级缓存流程如下图
- 第一次发起查询用户 id 为 1 的用户信息,Mybatis 会先去找缓存中是否有 id 为 1 的用户信息,如果没有,从数据库查询用户信息。
- 得到用户信息,将用户信息存储到一级缓存中。
- 如果
sqlSession
去执行 commit 操作(执行插入、更新、删除),那么 Mybatis 就会清空 SqlSession 中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。 - 第二次发起查询用户 id 为 1 的用户信息,先去找缓存中是否有 id 为 1 的用户信息,缓存中有,直接从缓存中获取用户信息。
- Mybatis 默认就是使用一次缓存的,不需要配置。
- 一级缓存中存放的是对象。(一级缓存其实就是 Map 结构,直接存放对象)
二级缓存
二级缓存是 Mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 SQL 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
二级缓存流程如下图
- 当
sqlSession1
去查询用户信息的时候,Mybatis 会将查询数据存储到二级缓存中。 - 如果
sqlSession3
去执行相同 Mapper 映射下的 SQL 语句,并且执行 commit 提交,那么 Mybatis 将会清空该 Mapper 映射下的二级缓存区域的数据。 sqlSession2
去查询与sqlSession1
相同的用户信息,Mybatis 首先会去缓存中找是否存在数据,如果存在直接从缓存中取出数据。
- 当
流程
首先在 Mybatis 配置文件中添加配置 (这一步其实可以忽略,因为默认值为 true)
1 |
|
- 接着在映射文件中配置
1 |
|
- 最后在需要使用二级缓存的操作上配置
(针对每次查询都需要最新数据的操作,要设置成
useCache="false"
,禁用二级缓存)
1 |
|
- 当我们使用二级缓存的时候,所缓存的类一定要实现
java.io.Serializable
接口,这样才可以使用序列化的方式来保存对象。- 由于是序列化保存对象,所以二级缓存中存放的是数据,而不是整个对象。
Mybatis注解开发
- 在 Mybatis 的注解开发中,常用的注解如下表所示:
注解 | 作用 |
---|---|
@Intsert |
实现新增 |
@Update |
实现更新 |
@Delete |
实现删除 |
@Select |
实现查询 |
@Results |
实现结果集封装 |
@ResultMap |
实现引用 @Results 定义的封装 |
@One |
实现一对一结果集封装 |
@Many |
实现一对多结果集封装 |
@SelectProvider |
实现动态 SQL 映射 |
@CacheNamespace |
实现注解二级缓存的使用 |
注意,如果此时实体类的属性与数据库表列名不一致,那么我们应该使用@Results、@Result、@ResultMap
等注解
1 |
|
``` @Results
1
2
3
注解用于定义映射结果集,相当于标签1
2
3
4
5
6
- 其中,`id` 属性为唯一标识。
- `value` 属性用于接收 `@Result[]` 注解类型的数组。
- ```
@Result注解用于定义映射关系,相当于标签
1
<id />
和
1
<result />
- 其中,
id
属性指定主键。 property
属性指定实体类的属性名,column
属性指定数据库表中对应的列。
- 其中,
@ResultMap
注解用于引用@Results
定义的映射结果集,避免了重复定义映射结果集。
4.3 Mybatis 使用注解实现一对多
1 |
|
``` @Many
1
2
3
注解相当于标签1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
是多表查询的关键,在注解中用来指定子查询返回对象集合
- 其中,`select` 属性指定用于查询的接口方法,`fetchType` 属性用于指定**立即加载或延迟加载**,分别对应 `FetchType.EAGER` 和 `FetchType.LAZY`
- 在包含 `@Many` 注解的 `@Result` 中,`column` 属性用于指定将要作为**参数进行查询的数据库**表列。
### 4.4 Mybatis 使用注解实现二级缓存
- 如果使用注解时想开启二级缓存,那么首先应该在 Mybatis 配置文件中开启全局配置
```xml
<settings>
<!-- 开启缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>接着在持久层接口中使用注解即可
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!