Mybatis
万物皆可官方文档:https://mybatis.org/mybatis-3/zh/index.html
**前记:**之前为了一个工作室的考核,匆匆学了Mybatis-Spring,基本的使用会了。借着寒假,把Mybatis的使用系统过了一遍,对以前使用不明白的地方进行再理解。
一、基本
- MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
- 持久层框架,工作在持久层,做持久化工作
- 特点:
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射
- 提供对象关系映射标签,支持对象关系组建维护
- 提供xml标签,支持编写动态sql。
二、第一个mybatis程序
1、环境准备
<?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/music_shop?useSSL=true&useUnicode=true&charaEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--为mapper的xml文件指定位置-->
<mappers>
<mapper resource="mybatis/mapper/songMapper.xml"/>
</mappers>
</configuration>
2、编写类
pojo
public class Song {
private Integer id;
private String name;
public Song() {
}
@Override
public String toString() {
return "Song{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Song(Integer id, String name) {
this.id = id;
this.name = name;
}
}
mapper接口
public interface SongMapper {
List<Song> getSongList();
}
<?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">
<mapper namespace="com.hao.mapper.SongMapper">
<select id="getSongList" resultType="com.hao.pojo.Song">
select * from music_shop.song;
</select>
</mapper>
3、得到sqlSession
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory = null;
static {
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
4、测试
public class TestUsage {
@Test
public void test() {
//方式1, 通过sqlSession的getMapper方法
SqlSession sqlSession = MybatisUtil.getSqlSession();
SongMapper mapper = sqlSession.getMapper(SongMapper.class);
List<Song> songList = mapper.getSongList();
for (Song song : songList) {
System.out.println(song);
}
//方式2, 通过sqlSession的具体方法
List<Song> songs = sqlSession.selectList("com.hao.mapper.SongMapper.getSongList");
for (Song song : songs) {
System.out.println(song);
}
}
}
三、CURD
1、基本使用
基本使用已经在SSM项目中大量应用了,这里记录几个点:
(xml方式)
-
#{id}配合@Param(“id”)可以指定传入参数
-
返回值和参数是基本类型可以不用写。但 最好还是写,万一参数是int, 而实体类中定义了long就麻烦了。如果是int和String。。。
-
如果参数是对象,则取的时候可以直接#{对象具体属性}
-
resources中必需是斜杠,因为是路径
<mappers> <mapper resource="mybatis/mapper/songMapper.xml"/> </mappers>
-
中文乱码问题:在idea中改一下文件的编码方式
2、map
当实体类的字段特别多的时候,要用到简化的实体类对象。(dto也可以实现相同的功能)
用map可以使传入参数更加灵活。
比如map中有如下键值对:
a: A, b:B, c:C相当于方法中有这样的参数 方法(int a, int b, int c)
这样的话,#{a}取的值是一模一样的,参数相当于键名,传入的值相当于键值对中的值。
直接利用map的键值对来取值。
int addSongThroughMap(Map<String, Object> map);
<!--注意这里,参数类型parameterType直接写map即可-->
<insert id="addSongThroughMap" parameterType="map">
insert into music_shop.song (`artist`,`name`) values (#{songArtist},#{songName});
</insert>
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
SongMapper mapper = sqlSession.getMapper(SongMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("songArtist", "测试作者");
map.put("songName", "测试歌曲");
mapper.addSongThroughMap(map);
sqlSession.commit();
sqlSession.close();
}
3、模糊查询
方式一:利用java拼接
List<Song> findSongByRoughName(@Param("value") String value);
<select id="findSongByRoughName" parameterType="String" resultType="com.hao.pojo.Song">
select * from music_shop.song where `name` like #{value};
</select>
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
SongMapper mapper = sqlSession.getMapper(SongMapper.class);
List<Song> songs = mapper.findSongByRoughName("%我%");
for (Song song : songs) {
System.out.println(song);
}
}
方式二:在xml文件中写死
比较安全
<select id="findSongByRoughName" parameterType="String" resultType="com.hao.pojo.Song">
select * from music_shop.song where `name` like "%"#{value}"%";
</select>
<select id="findSongByRoughName" parameterType="String" resultType="com.hao.pojo.Song">
select * from music_shop.song where `name` like '%${value}%';
</select>
4、ResultMap
-
解决查询出来的字段名和实体属性名不一致的问题。
-
在底层上,查询出来的列名是通过实体类的set和get方法实现的。(只要get和set方法名与列名一致就行??)
-
方式二和方式一在本质上是一样的。
- 方式一只是简单地将所有的列映射到
HashMap
的键上,这由resultType
属性指定 - 而方式二是MyBatis 会在幕后自动创建一个
ResultMap
,再根据属性名来映射列到 JavaBean 的属性上,即显式说明哈希表中键的名称
- 方式一只是简单地将所有的列映射到
解决方式一:给查询列起别名
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
解决方式二:利用resultMap标签。
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
5、分页
方式一和方式二的区别:
- 方式一是物理分页,方式二是逻辑分页
- 从方式二的日志来看,在数据库中共查询出了6条语句,而RowBound把它分割,只取出要的5个。
- 而方式一就只查询出想要的
方式一:使用limit
<select id="getSongList" resultType="com.hao.pojo.Song">
select * from music_shop.song limit 4,5;<!--limit参数在开发中用方法传,这里测试就不麻烦了-->
</select>
Opening JDBC Connection
Created connection 1413378318.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e]
==> Preparing: select * from music_shop.song limit 4,5;
==> Parameters:
<== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<== Row: 25, MV 交换余生, 林俊杰, 2, 1, 2020-09-04, 12, null, 0
<== Row: 26, 望穿, 陈粒, 6, 1, 2010-01-04, 21.2, null, 0
<== Row: 27, 情景剧, 陈粒, 3, 2, 2014-10-04, 24, null, 0
<== Row: 28, 空舞, 陈粒, 3, 3, 2010-09-05, 21, null, 0
<== Row: 29, 夏之恋, 旅行团乐队, 4, 1, 2020-01-04, 11, null, 0
<== Total: 5
Song{id=25, name=' MV 交换余生'}
Song{id=26, name='望穿'}
Song{id=27, name='情景剧'}
Song{id=28, name='空舞'}
Song{id=29, name='夏之恋'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@543e710e]
Returned connection 1413378318 to pool.
Process finished with exit code 0
方式二:RowBound
<!--直接不用limit-->
<select id="getSongList" resultType="com.hao.pojo.Song">
select * from music_shop.song;
</select>
@Test
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
RowBounds rowBounds = new RowBounds(4, 5);
List<Song> songs = sqlSession.selectList("com.hao.mapper.SongMapper.getSongList", null, rowBounds);
for (Song song : songs) {
System.out.println(song);
}
sqlSession.close();
}
Opening JDBC Connection
Created connection 22756955.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15b3e5b]
==> Preparing: select * from music_shop.song;
==> Parameters:
<== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<== Row: 21, 苦甜, BOY STORY, 1, 1, 2020-01-04, 20.4, null, 1
<== Row: 22, Track 02, BOY STORY, 0, 1, 2020-01-04, 12, null, 1
<== Row: 23, Track 02, BOY STORY, 0, 2, 2020-12-04, 12.5, null, 0
<== Row: 24, MV 交换余生, 林俊杰, 0, 1, 2020-09-04, 12.4, null, 1
<== Row: 25, MV 交换余生, 林俊杰, 2, 1, 2020-09-04, 12, null, 0
<== Row: 26, 望穿, 陈粒, 6, 1, 2010-01-04, 21.2, null, 0
<== Row: 27, 情景剧, 陈粒, 3, 2, 2014-10-04, 24, null, 0
<== Row: 28, 空舞, 陈粒, 3, 3, 2010-09-05, 21, null, 0
<== Row: 29, 夏之恋, 旅行团乐队, 4, 1, 2020-01-04, 11, null, 0
Song{id=25, name=' MV 交换余生'}
Song{id=26, name='望穿'}
Song{id=27, name='情景剧'}
Song{id=28, name='空舞'}
Song{id=29, name='夏之恋'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@15b3e5b]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@15b3e5b]
Returned connection 22756955 to pool.
方式三:分页插件,pateHelper
方式四:前端分页
四、配置
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
0、注意点
-
The content of element type “configuration” must match “(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)”.
在标签中,其子标签必需要符合一定的顺序
-
注意maven静态资源过滤问题
-
#{}和${}的区别是什么
a、#{}是预编译处理,${}是字符串替换。
b、Mybatis 在处理#{}时,会将 sql 中的#{}替换为?号,调用 PreparedStatement 的 set 方法来赋值;
c、Mybatis 在处理 时 , 就 是 把 {}时,就是把 时,就是把{}替换成变量的值。
d、使用#{}可以有效的防止 SQL 注入,提高系统安全性。对于JDBC而言,SQL注入攻击只对Statement有效,对PreparedStatement是无效的,这是因为PreparedStatement不允许在插入时改变查询的逻辑结构。
-
resultType底层还是用map实现的
1、enviroment
<!--在default这里可以选择环境-->
<environments default="development1">
<environment id="development1">
<!--默认的事务管理是jdbc。mybatis总共有两套事务管理,分别是jdbc和MANAGED-->
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<!--数据源默认有三种连接方式:type="[UNPOOLED|POOLED|JNDI]"-->
<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>
<!--这里可以配置多套环境-->
<environment id="development2">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
2、properties
<!--引入外部配置文件,就可以在本配置文件中直接用$符直接取属性值-->
<properties resource="db.properties"/>
<!--可以在本配置文件中添加其他属性。
如果和外部配置文件复合,则优先使用外部配置文件,即本配置内属性被外部文件覆盖!!-->
<properties resource="db.properties">
<property name="username" value="root"/>
</properties>
3、typeAliases
alias别名,化名
实体类比较少用第一种方式,多用第二种。
<!--方式一,每个类起别名-->
<typeAliases>
<typeAlias type="com.hao.pojo.User" alias="User"/>
<typeAlias type="com.hao.pojo.Song" alias="Song"/>
</typeAliases>
<!--方式二,为整个包内的类起别名。
默认是类的首字母小写。大写也可以。。。
如User类, 可以用User别名和user别名-->
<typeAliases>
<package name="com.hao.pojo"/>
</typeAliases>
<!--方式三,通过注解。要先指定扫描包,即要配合第二种方式使用,即起到覆盖 DIY的作用。-->
@Alias("user")
class user{}
下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。
规律:
- 基本类型的别名是在原名前面加"_" ,
- 基本类型的包装类型则直接小写
- 集合框架相关也直接小写。
别名 | 映射的类型 |
---|---|
_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 |
4、settings
5、mappers映射器
MapperRegistry: 注册绑定我们的Mapper.xml文件
不写的话会提示在MapperRegistry找不到注册的mapper
mapper --> 找到mapper.xml --> 通过命名空间找到接口
方式一:
<!-- 使用相对于类路径的资源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式二:不用
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/BlogMapper.xml"/>
<mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
方式三:
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
<mapper class="com.hao.mapper.AuthorMapper"/>
<mapper class="com.hao.mapper.BlogMapper"/>
<mapper class="com.hao.mapper.PostMapper"/>
</mappers>
方式四:
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
<package name="com.hao.mapper"/>
</mappers>
方式三和方式四中:
- 接口和它的mapper配置文件必须同名
- 接口和它的mapper配置文件必须在同一个包下
6、生命周期和作用域
作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
SqlSessionFactoryBuilder:
- 一旦创建了 SqlSessionFactory,就不再需要它了
- 最佳作用域是方法作用域(也就是局部方法变量)
SqlSessionFactory:
- 单例模式或者静态单例模式
- 最佳作用域是应用作用域
SqlSession:
- 不是线程安全的
- 每个线程都应该有它自己的 SqlSession 实例,使用完后一定要关闭
- 最佳的作用域是请求或方法作用域
SqlSessionFactory相当于“池”的概念,每次可以从里面得到SqlSession对象,然后用SqlSession对象进行具体的业务操作。
五、日志
设置名 | 描述 | 有效值 | 默认值 |
---|---|---|---|
logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未设置,代表着默认关闭日志 |
<settings>
<setting name="logImpl" value="有效值"/>
</settings>
1、标准日志工厂
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
控制台输出如下:
Opening JDBC Connection
Created connection 225290371.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@d6da883]
==> Preparing: select * from music_shop.song where `name` like '%我%';
==> Parameters:
<== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<== Row: 31, 我们存在一刹那的喜欢, 陈粒, 3, 0, 2010-10-04, 11, null, 0
<== Row: 32, 我的蒙娜丽莎, 旅行团乐队, 4, 4, 2010-09-02, 21, null, 0
<== Total: 2
Song{id=31, name='我们存在一刹那的喜欢'}
Song{id=32, name='我的蒙娜丽莎'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@d6da883]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@d6da883]
Returned connection 225290371 to pool.
2、log4j
六、使用注解开发
1、注解CURD
注意的是,mapper需要在MapperRegistry注册。如果没有mapper.xml文件,则使用package或class的mapper方式来注册。
可以看出,使用注解和不使用注解可以混合使用。
public interface SongMapper {
List<Song> getSongList();
int addSongThroughMap(Map<String, Object> map);
List<Song> findSongByRoughName(@Param("value") String value);
@Select("select * from song")
List<Song> getSongList2();
@Update("update song set name=#{name} where id=#{id}")
int updateSong(Song song);
@Insert("insert into song(`id`,`name`) value(#{sid},#{name}")
int insertSong(@Param("name") String name, @Param("sid") int id);
@Delete("delete from song where id=#{id}")
int deleteSong(@Param("id") int id);
}
七、复杂对应关系
本质:解决查询出来的字段与实体类中属性名不一致的问题
1、多对一
关联
测试实体:song和album
@Data
public class Song {
private Integer id;
private String name;
private Album album;
}
@Data
public class Album {
private Integer id;
private String name;
}
方式一:联合查询
<select id="getSongList" resultMap="songMap">
select
s.id song_id,
s.name song_name,
a.id album_id,
a.name album_name
from song s,album a
where s.`album_id` = a.id;
</select>
<resultMap id="songMap" type="com.hao.pojo.Song">
<id property="id" column="song_id"/>
<result property="name" column="song_name"/>
<association property="album" javaType="com.hao.pojo.Album">
<id property="id" column="album_id"/>
<result property="name" column="album_name"/>
</association>
</resultMap>
Opening JDBC Connection
Created connection 1024429571.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3d0f8e03]
==> Preparing: select s.id song_id, s.name song_name, a.id album_id, a.name album_name from song s,album a where s.`album_id` = a.id;
==> Parameters:
<== Columns: song_id, song_name, album_id, album_name
<== Row: 25, MV 交换余生, 2, 交换余生
<== Row: 26, 望穿, 6, 笔记本子232
<== Row: 27, 情景剧, 3, 玩
<== Row: 28, 空舞, 3, 玩
<== Row: 29, 夏之恋, 4, 感++
<== Row: 30, 终结日, 4, 感++
<== Row: 31, 我们存在一刹那的喜欢, 3, 玩
<== Row: 32, 我的蒙娜丽莎, 4, 感++
<== Row: 33, 周末玩具, 5, sdf
<== Row: 35, 你好, 4, 感++
<== Row: 36, 你好, 4, 感++
<== Total: 11
Song(id=25, name= MV 交换余生, album=Album(id=2, name=交换余生))
Song(id=26, name=望穿, album=Album(id=6, name=笔记本子232))
Song(id=27, name=情景剧, album=Album(id=3, name=玩))
Song(id=28, name=空舞, album=Album(id=3, name=玩))
Song(id=29, name=夏之恋, album=Album(id=4, name=感++))
Song(id=30, name=终结日, album=Album(id=4, name=感++))
Song(id=31, name=我们存在一刹那的喜欢, album=Album(id=3, name=玩))
Song(id=32, name=我的蒙娜丽莎, album=Album(id=4, name=感++))
Song(id=33, name=周末玩具, album=Album(id=5, name=sdf))
Song(id=35, name=你好, album=Album(id=4, name=感++))
Song(id=36, name=你好, album=Album(id=4, name=感++))
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3d0f8e03]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3d0f8e03]
Returned connection 1024429571 to pool.
方式二:子查询
<select id="getSongList" resultMap="songMap">
select `id`,`name`,`album_id`
from song;
</select>
<resultMap id="songMap" type="Song">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--colum中的album_id会作为参数,传入到第二个select标签中的#{album_id}-->
<association property="album" javaType="Album" column="album_id" select="getAlbum"/>
</resultMap>
<!--这个select标签中,songMapper接口中不一定有这个方法-->
<select id="getAlbum" resultType="Album">
select * from album where `id`=#{album_id};
</select>
Opening JDBC Connection
Created connection 1601292138.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5f71c76a]
==> Preparing: select `id`,`name`,`album_id` from song;
==> Parameters:
<== Columns: id, name, album_id
<== Row: 21, 苦甜, 1
====> Preparing: select * from album where `id`=?;
====> Parameters: 1(Integer)
<==== Total: 0
<== Row: 22, Track 02, 0
====> Preparing: select * from album where `id`=?;
====> Parameters: 0(Integer)
<==== Total: 0
<== Row: 23, Track 02, 0
<== Row: 24, MV 交换余生, 0
<== Row: 25, MV 交换余生, 2
====> Preparing: select * from album where `id`=?;
====> Parameters: 2(Integer)
<==== Columns: id, name, artist, introduction, issue_date
<==== Row: 2, 交换余生, 林俊杰, 尽管命运的曲折,为了所爱奋不顾身 愿交换余生,只求存在彼此一生。 无论身处任一时空,都要紧握着的手 愿交换余生,只求此刻还能相依相守。 交换余生,是深深相爱的义无反顾。 有分合、有失去、有遗憾、有重新面对人生 蕴含着每份情感的托付。, 2020-09-16
<==== Total: 1
<== Row: 26, 望穿, 6
====> Preparing: select * from album where `id`=?;
====> Parameters: 6(Integer)
<==== Columns: id, name, artist, introduction, issue_date
<==== Row: 6, 笔记本子232, 23, 舌里低脂全麦黑麦代餐面包1000g粗粮健身餐吐司糕点休闲零食, 2020-12-02
<==== Total: 1
<== Row: 27, 情景剧, 3
====> Preparing: select * from album where `id`=?;
====> Parameters: 3(Integer)
<==== Columns: id, name, artist, introduction, issue_date
<==== Row: 3, 玩, 陈粒, “玩”是一种古老的手段,贪图短暂一乐。 奥德赛不仅是史诗,更是世界上第一台游戏机的名字;你已经记不清长岛冰茶之前,还有哪款鸡尾酒能让人们为之欢呼;, 2019-09-05
<==== Total: 1
<== Row: 28, 空舞, 3
<== Row: 29, 夏之恋, 4
====> Preparing: select * from album where `id`=?;
====> Parameters: 4(Integer)
<==== Columns: id, name, artist, introduction, issue_date
<==== Row: 4, 感++, 旅行团乐队, 我们看似生活在同一个世界,每天面对着同样的钢筋水泥而忙忙碌碌,互联网飞速运转的大数据记录着我们的一切;但我们又好像都生活在不同的世界里, 2020-10-04
<==== Total: 1
<== Row: 30, 终结日, 4
<== Row: 31, 我们存在一刹那的喜欢, 3
<== Row: 32, 我的蒙娜丽莎, 4
<== Row: 33, 周末玩具, 5
====> Preparing: select * from album where `id`=?;
====> Parameters: 5(Integer)
<==== Columns: id, name, artist, introduction, issue_date
<==== Row: 5, sdf, 陈粒, 华农酸奶,好喝, 2020-12-04
<==== Total: 1
<== Row: 34, 笔记本子, 0
<== Row: 35, 你好, 4
<== Row: 36, 你好, 4
<== Row: 38, ?????, 0
<== Row: 39, 344, 0
<== Row: 40, 测试歌曲, 0
<== Total: 19
Song(id=21, name=苦甜, album=null)
Song(id=22, name=Track 02, album=null)
Song(id=23, name=Track 02, album=null)
Song(id=24, name= MV 交换余生, album=null)
Song(id=25, name= MV 交换余生, album=Album(id=2, name=交换余生))
Song(id=26, name=望穿, album=Album(id=6, name=笔记本子232))
Song(id=27, name=情景剧, album=Album(id=3, name=玩))
Song(id=28, name=空舞, album=Album(id=3, name=玩))
Song(id=29, name=夏之恋, album=Album(id=4, name=感++))
Song(id=30, name=终结日, album=Album(id=4, name=感++))
Song(id=31, name=我们存在一刹那的喜欢, album=Album(id=3, name=玩))
Song(id=32, name=我的蒙娜丽莎, album=Album(id=4, name=感++))
Song(id=33, name=周末玩具, album=Album(id=5, name=sdf))
Song(id=34, name=笔记本子, album=null)
Song(id=35, name=你好, album=Album(id=4, name=感++))
Song(id=36, name=你好, album=Album(id=4, name=感++))
Song(id=38, name=?????, album=null)
Song(id=39, name=344, album=null)
Song(id=40, name=测试歌曲, album=null)
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5f71c76a]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5f71c76a]
Returned connection 1601292138 to pool.
Process finished with exit code 0
注意点:
- 方式一由于没有用left join,所以专辑为空时查不出来
- 从日志中可以看出,方式二每次查出歌曲后,又从专辑表中再次查询了
- 方式二比方式一复用性更高,但映射关系处理没有比方式一好
- 子查询中,两个参数的情况就不知道怎么处理了。。。
- 两者性能上没有特意去研究,因为还没有上数据库原理这门课,目前就先学会用着吧。
2、一对多
集合
测试实体:song和album
@Data
public class Song {
private Integer id;
private String name;
private Integer albumId;
}
@Data
public class Album {
private Integer id;
private String name;
private List<Song> songList;
}
方式一:联合查询
<select id="getAlbumList" resultMap="albumMap">
select
a.id album_id,
a.name album_name,
s.id song_id,
s.name song_name
from album a, song s
where a.`id`=s.`album_id`
</select>
<resultMap id="albumMap" type="com.hao.pojo.Album">
<!--如果id有重复,则放入一个集合内-->
<id property="id" column="album_id"/> <!--注意a.id不是列别名,如果没有起别名还是叫id-->
<result property="name" column="album_name"/>
<collection property="songList" javaType="ArrayList" ofType="Song">
<id property="id" column="song_id"/>
<result property="name" column="song_name"/>
</collection>
</resultMap>
Opening JDBC Connection
Created connection 322836221.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@133e16fd]
==> Preparing: select a.id album_id, a.name album_name, s.id song_id, s.name song_name from album a, song s where a.`id`=s.`album_id`
==> Parameters:
<== Columns: album_id, album_name, song_id, song_name
<== Row: 2, 交换余生, 25, MV 交换余生
<== Row: 6, 笔记本子232, 26, 望穿
<== Row: 3, 玩, 27, 情景剧
<== Row: 3, 玩, 28, 空舞
<== Row: 4, 感++, 29, 夏之恋
<== Row: 4, 感++, 30, 终结日
<== Row: 3, 玩, 31, 我们存在一刹那的喜欢
<== Row: 4, 感++, 32, 我的蒙娜丽莎
<== Row: 5, sdf, 33, 周末玩具
<== Row: 4, 感++, 35, 你好
<== Row: 4, 感++, 36, 你好
<== Total: 11
Album(id=2, name=交换余生, songList=[Song(id=25, name= MV 交换余生, albumId=null)])
Album(id=6, name=笔记本子232, songList=[Song(id=26, name=望穿, albumId=null)])
Album(id=3, name=玩, songList=[Song(id=27, name=情景剧, albumId=null), Song(id=28, name=空舞, albumId=null), Song(id=31, name=我们存在一刹那的喜欢, albumId=null)])
Album(id=4, name=感++, songList=[Song(id=29, name=夏之恋, albumId=null), Song(id=30, name=终结日, albumId=null), Song(id=32, name=我的蒙娜丽莎, albumId=null), Song(id=35, name=你好, albumId=null), Song(id=36, name=你好, albumId=null)])
Album(id=5, name=sdf, songList=[Song(id=33, name=周末玩具, albumId=null)])
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@133e16fd]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@133e16fd]
Returned connection 322836221 to pool.
Process finished with exit code 0
方式二:子查询
<select id="getAlbumList" resultMap="albumMap">
select *
from album;
</select>
<resultMap id="albumMap" type="Album">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- collection标签中的column会作为参数,传入到子查询中.
两个参数怎么办?????-->
<collection property="songList" javaType="ArrayList" ofType="Song" column="id" select="getSong"/>
</resultMap>
<select id="getSong" resultType="Song">
select *
from song
where `album_id`=#{albumId}
</select>
Opening JDBC Connection
Created connection 322836221.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@133e16fd]
==> Preparing: select * from album;
==> Parameters:
<== Columns: id, name, artist, introduction, issue_date
<== Row: 2, 交换余生, 林俊杰, 尽管命运的曲折,为了所爱奋不顾身 愿交换余生,只求存在彼此一生。 无论身处任一时空,都要紧握着的手 愿交换余生,只求此刻还能相依相守。 交换余生,是深深相爱的义无反顾。 有分合、有失去、有遗憾、有重新面对人生 蕴含着每份情感的托付。, 2020-09-16
====> Preparing: select * from song where `album_id`=?
====> Parameters: 2(Integer)
<==== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<==== Row: 25, MV 交换余生, 林俊杰, 2, 1, 2020-09-04, 12, null, 0
<==== Total: 1
<== Row: 3, 玩, 陈粒, “玩”是一种古老的手段,贪图短暂一乐。 奥德赛不仅是史诗,更是世界上第一台游戏机的名字;你已经记不清长岛冰茶之前,还有哪款鸡尾酒能让人们为之欢呼;, 2019-09-05
====> Preparing: select * from song where `album_id`=?
====> Parameters: 3(Integer)
<==== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<==== Row: 27, 情景剧, 陈粒, 3, 2, 2014-10-04, 24, null, 0
<==== Row: 28, 空舞, 陈粒, 3, 3, 2010-09-05, 21, null, 0
<==== Row: 31, 我们存在一刹那的喜欢, 陈粒, 3, 0, 2010-10-04, 11, null, 0
<==== Total: 3
<== Row: 4, 感++, 旅行团乐队, 我们看似生活在同一个世界,每天面对着同样的钢筋水泥而忙忙碌碌,互联网飞速运转的大数据记录着我们的一切;但我们又好像都生活在不同的世界里, 2020-10-04
====> Preparing: select * from song where `album_id`=?
====> Parameters: 4(Integer)
<==== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<==== Row: 29, 夏之恋, 旅行团乐队, 4, 1, 2020-01-04, 11, null, 0
<==== Row: 30, 终结日, 旅行团乐队, 4, 3, 2010-09-05, 4, null, 0
<==== Row: 32, 我的蒙娜丽莎, 旅行团乐队, 4, 4, 2010-09-02, 21, null, 0
<==== Row: 35, 你好, 旅行团乐队, 4, 4, 2023-10-05, 12, null, 1
<==== Row: 36, 你好, 2323, 4, 0, 2023-09-02, 11, null, 0
<==== Total: 5
<== Row: 5, sdf, 陈粒, 华农酸奶,好喝, 2020-12-04
====> Preparing: select * from song where `album_id`=?
====> Parameters: 5(Integer)
<==== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<==== Row: 33, 周末玩具, 黄涯, 5, 1, 2010-01-02, 12.5, null, 0
<==== Total: 1
<== Row: 6, 笔记本子232, 23, 舌里低脂全麦黑麦代餐面包1000g粗粮健身餐吐司糕点休闲零食, 2020-12-02
====> Preparing: select * from song where `album_id`=?
====> Parameters: 6(Integer)
<==== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<==== Row: 26, 望穿, 陈粒, 6, 1, 2010-01-04, 21.2, null, 0
<==== Total: 1
<== Total: 5
Album(id=2, name=交换余生, songList=[Song(id=25, name= MV 交换余生, albumId=null)])
Album(id=3, name=玩, songList=[Song(id=27, name=情景剧, albumId=null), Song(id=28, name=空舞, albumId=null), Song(id=31, name=我们存在一刹那的喜欢, albumId=null)])
Album(id=4, name=感++, songList=[Song(id=29, name=夏之恋, albumId=null), Song(id=30, name=终结日, albumId=null), Song(id=32, name=我的蒙娜丽莎, albumId=null), Song(id=35, name=你好, albumId=null), Song(id=36, name=你好, albumId=null)])
Album(id=5, name=sdf, songList=[Song(id=33, name=周末玩具, albumId=null)])
Album(id=6, name=笔记本子232, songList=[Song(id=26, name=望穿, albumId=null)])
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@133e16fd]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@133e16fd]
Returned connection 322836221 to pool.
Process finished with exit code 0
八、动态sql
一句话:根据不同条件执行不同sql
一句话:根据不同条件执行结构相同但意义不同的sql
1、IF
public interface SongMapper {
List<Song> findSongByIF(Map<String, Object> map);
}
<select id="findSongByIF" resultType="Song" parameterType="map">
select *
from song
<!--如果传入的id不为空,则根据id查-->
<if test="id != null">
where id = #{id}
</if>
<!--如果传入的id为空,且传入的名字不为空,则根据名字模糊查-->
<if test="id == null and name != null">
where `name` like "%" #{name} "%"
</if>
<!--如果都为null,则直接查询全部-->
</select>
@Test
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
SongMapper mapper = sqlSession.getMapper(SongMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
//只传id
map.put("id", "23");
List<Song> songs = mapper.findSongByIF(map);
for (Song song : songs) {
System.out.println(song);
}
sqlSession.close();
}
/**
结果:
Song(id=23, name=Track 02, albumId=null)
**/
@Test
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
SongMapper mapper = sqlSession.getMapper(SongMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
//啥都不传入,都为null
List<Song> songs = mapper.findSongByIF(map);
for (Song song : songs) {
System.out.println(song);
}
sqlSession.close();
}
/**
Song(id=21, name=苦甜, albumId=null)
Song(id=22, name=Track 02, albumId=null)
Song(id=23, name=Track 02, albumId=null)
Song(id=24, name= MV 交换余生, albumId=null)
Song(id=25, name= MV 交换余生, albumId=null)
Song(id=26, name=望穿, albumId=null)
Song(id=27, name=情景剧, albumId=null)
Song(id=28, name=空舞, albumId=null)
Song(id=29, name=夏之恋, albumId=null)
Song(id=30, name=终结日, albumId=null)
Song(id=31, name=我们存在一刹那的喜欢, albumId=null)
Song(id=32, name=我的蒙娜丽莎, albumId=null)
Song(id=33, name=周末玩具, albumId=null)
Song(id=34, name=笔记本子, albumId=null)
Song(id=35, name=你好, albumId=null)
Song(id=36, name=你好, albumId=null)
Song(id=38, name=?????, albumId=null)
Song(id=39, name=344, albumId=null)
Song(id=40, name=测试歌曲, albumId=null)
**/
@Test
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
SongMapper mapper = sqlSession.getMapper(SongMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
//只传入名字
map.put("name", "我");
List<Song> songs = mapper.findSongByIF(map);
for (Song song : songs) {
System.out.println(song);
}
sqlSession.close();
}
/**
结果:
Song(id=31, name=我们存在一刹那的喜欢, albumId=null)
Song(id=32, name=我的蒙娜丽莎, albumId=null)
**/
2、where
解决拼接问题:
<select id="findSongByIF" resultType="Song" parameterType="map">
select *
from song
where 1=1
<!--如果传入的id不为空,则根据id查-->
<if test="id != null">
and id = #{id}
</if>
<!--如果传入的id为空,且传入的名字不为空,则根据名字模糊查-->
<if test="id == null and name != null">
and `name` like "%" #{name} "%"
</if>
<!--如果都为null,则直接查询全部-->
</select>
3、choose
它有点像 Java 中的 switch 语句
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<!--两个when都不满足时-->
<otherwise>
AND featured = 1
</otherwise>
</choose>
</where>
</select>
4、set
解决更新字段时,的逗号问题
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
5、trim
自定义标签:
和set标签等价:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
和where标签等价:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
可以直接用trim替代where:
<select id="findSongByIF" resultType="Song" parameterType="map">
select *
from song
<!--如果传入的id不为空,则根据id查-->
<!--这里的trim相当于<where>标签-->
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="id != null">
and id = #{id}
</if>
<!--如果传入的id为空,且传入的名字不为空,则根据名字模糊查-->
<if test="id == null and name != null">
and `name` like "%" #{name} "%"
</if>
</trim>
<!--如果都为null,则直接查询全部-->
</select>
6、Foreach
public interface SongMapper {
List<Song> findSongBySomeId(@Param("idList") List<Integer> idList);
}
<!--
从日志中可以看出,最后拼接出来的结果是:
select * from song WHERE ( `id` = ? or `id` = ? or `id` = ? )
-->
<select id="findSongBySomeId" parameterType="list" resultType="Song">
select *
from song
<where>
<foreach collection="idList" item="getId"
open="(" separator="or" close=")">
`id` = #{getId}
</foreach>
</where>
</select>
<!--单纯从sql层面上来说,上面的xml等价于下面的xml
即:select * from song WHERE `id` in (?,?,?)
-->
<select id="findSongBySomeId" parameterType="list" resultType="Song">
select *
from song
<where>
`id` in
<foreach collection="idList" item="getId"
open="(" separator="," close=")">
#{getId}
</foreach>
</where>
</select>
@Test
public void test() {
SqlSession sqlSession = MybatisUtil.getSqlSession();
SongMapper mapper = sqlSession.getMapper(SongMapper.class);
ArrayList<Integer> integers = new ArrayList<Integer>();
integers.add(23);
integers.add(25);
integers.add(26);
List<Song> songBySomeId = mapper.findSongBySomeId(integers);
sqlSession.close();
}
Opening JDBC Connection
Created connection 302870502.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@120d6fe6]
==> Preparing: select * from song WHERE ( `id` = ? or `id` = ? or `id` = ? )
==> Parameters: 23(Integer), 25(Integer), 26(Integer)
<== Columns: id, name, artist, album_id, number_in_album, issue_date, price, entity_address, is_delete
<== Row: 23, Track 02, BOY STORY, 0, 2, 2020-12-04, 12.5, null, 0
<== Row: 25, MV 交换余生, 林俊杰, 2, 1, 2020-09-04, 12, null, 0
<== Row: 26, 望穿, 陈粒, 6, 1, 2010-01-04, 21.2, null, 0
<== Total: 3
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@120d6fe6]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@120d6fe6]
Returned connection 302870502 to pool.
Process finished with exit code 0
7、sql片段
可以类比前端中的组件、模板,提取重复片段,实现复用。
<select id="findSongByIF" resultType="Song" parameterType="map">
select *
from song
<where>
<include refid="testSql"/>
</where>
</select>
<sql id="testSql">
<if test="id != null">
and id = #{id}
</if>
<if test="id == null and name != null">
and `name` like "%" #{name} "%"
</if>
</sql>
甚至可以这样:
<select id="findSongByIF" resultType="Song" parameterType="map">
select *
<include refid="testSql"/>
</select>
<sql id="testSql">
from song <!--把一部分放到这里来-->
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="id == null and name != null">
and `name` like "%" #{name} "%"
</if>
</where>
</sql>
九、缓存
1、概述
-
-
缓存是存在内存中的临时数据
-
将用户经常查询的数据放在内存中,用户去查询数据就不用从磁盘上查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
-
为什么使用缓存:减少数据库的交互次数,减少系统开销,提高系统效率
-
什么样的数据可以使用或者说适合用缓存:经常查询并且不经常改变的数据
-
2、一级缓存
- 默认情况下只有一级缓存开启
- SqlSession级别的缓存,也叫本地缓存
- 与数据库同一次会话期间查询到的数据会放在本地缓存中
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库
- 可以理解了map
缓存失效:
- 查询不同的东西
- 增删改操作,因为可以会改变原来的数据,所以必定会刷新缓存
- 通过不同的mapper.xml文件查。(连二级缓存都没有)即两个mapper.xml查询出来的东西一样,也不会进行缓存
- 手动清理缓存sqlSession.clearCache()方法
3、二级缓存
- 二级缓存也叫全局缓存,一级缓存作用域太低,所以诞生了二级缓存
- 基于namespace级别的缓存,一个命名空间对应一个二级缓存
- 工作机制:
- 一个会话查询一条数据,这个数据应付被放在当前会话的一级缓存中
- 如果当前会话关闭了,这个会话对应的一级缓存就没了
- 如果开启了二级缓存,新的会话查询信息,就只可以从二级缓存中获取内容
- 不同的mapper查出的数据会放在自己对应的缓存(map)中
使用:
在主配置文件中开启缓存
不加也可以,因为默认开启。
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
在mapper.xml文件中加入缓存开关
如果readOnly参数为false(默认为false),对象要实现序列化接口。
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
或
<cache/>
测试一
@Test
public void test() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
SongMapper mapper1 = sqlSession1.getMapper(SongMapper.class);
SongMapper mapper2 = sqlSession2.getMapper(SongMapper.class);
List<Song> songList1 = mapper1.getSongList();
List<Song> songList2 = mapper2.getSongList();
System.out.println("Debug==>" + (songList1 == songList2));
sqlSession1.close();
sqlSession2.close();
}
/**
Opening JDBC Connection
Created connection 1904324159.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7181ae3f]
==> Preparing: select `id`,`name`,`album_id` from song;
==> Parameters:
<== Columns: id, name, album_id
<== Row: 21, 苦甜, 1
<== Row: 22, Track 02, 0
<== Row: 23, Track 02, 0
<== Row: 24, MV 交换余生, 0
<== Row: 25, MV 交换余生, 2
<== Row: 26, 望穿, 6
<== Row: 27, 情景剧, 3
<== Row: 28, 空舞, 3
<== Row: 29, 夏之恋, 4
<== Row: 30, 终结日, 4
<== Row: 31, 我们存在一刹那的喜欢, 3
<== Row: 32, 我的蒙娜丽莎, 4
<== Row: 33, 周末玩具, 5
<== Row: 34, 笔记本子, 0
<== Row: 35, 你好, 4
<== Row: 36, 你好, 4
<== Row: 38, ?????, 0
<== Row: 39, 344, 0
<== Row: 40, 测试歌曲, 0
<== Total: 19
Cache Hit Ratio [com.hao.mapper.SongMapper]: 0.0
Opening JDBC Connection
Created connection 306206744.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@12405818]
==> Preparing: select `id`,`name`,`album_id` from song;
==> Parameters:
<== Columns: id, name, album_id
<== Row: 21, 苦甜, 1
<== Row: 22, Track 02, 0
<== Row: 23, Track 02, 0
<== Row: 24, MV 交换余生, 0
<== Row: 25, MV 交换余生, 2
<== Row: 26, 望穿, 6
<== Row: 27, 情景剧, 3
<== Row: 28, 空舞, 3
<== Row: 29, 夏之恋, 4
<== Row: 30, 终结日, 4
<== Row: 31, 我们存在一刹那的喜欢, 3
<== Row: 32, 我的蒙娜丽莎, 4
<== Row: 33, 周末玩具, 5
<== Row: 34, 笔记本子, 0
<== Row: 35, 你好, 4
<== Row: 36, 你好, 4
<== Row: 38, ?????, 0
<== Row: 39, 344, 0
<== Row: 40, 测试歌曲, 0
<== Total: 19
Debug==>false
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7181ae3f]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7181ae3f]
Returned connection 1904324159 to pool.
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@12405818]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@12405818]
Returned connection 306206744 to pool.
Process finished with exit code 0
**/
测试二
将缓存作用域提升到二级缓存。
注意:此时readOnly参数为true
@Test
public void test() {
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
SongMapper mapper1 = sqlSession1.getMapper(SongMapper.class);
List<Song> songList1 = mapper1.getSongList();
sqlSession1.close();
SqlSession sqlSession2 = MybatisUtil.getSqlSession();
SongMapper mapper2 = sqlSession2.getMapper(SongMapper.class);
List<Song> songList2 = mapper2.getSongList();
sqlSession2.close();
System.out.println("Debug==>" + (songList1 == songList2));
}
/**
Opening JDBC Connection
Created connection 1904324159.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7181ae3f]
==> Preparing: select `id`,`name`,`album_id` from song;
==> Parameters:
<== Columns: id, name, album_id
<== Row: 21, 苦甜, 1
<== Row: 22, Track 02, 0
<== Row: 23, Track 02, 0
<== Row: 24, MV 交换余生, 0
<== Row: 25, MV 交换余生, 2
<== Row: 26, 望穿, 6
<== Row: 27, 情景剧, 3
<== Row: 28, 空舞, 3
<== Row: 29, 夏之恋, 4
<== Row: 30, 终结日, 4
<== Row: 31, 我们存在一刹那的喜欢, 3
<== Row: 32, 我的蒙娜丽莎, 4
<== Row: 33, 周末玩具, 5
<== Row: 34, 笔记本子, 0
<== Row: 35, 你好, 4
<== Row: 36, 你好, 4
<== Row: 38, ?????, 0
<== Row: 39, 344, 0
<== Row: 40, 测试歌曲, 0
<== Total: 19
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7181ae3f]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7181ae3f]
Returned connection 1904324159 to pool.
Cache Hit Ratio [com.hao.mapper.SongMapper]: 0.5
Debug==>true
Process finished with exit code 0
**/
测试结果分析:
-
如果sqlsession还没关闭,则缓存在一级缓存中。
只有sqlsession关闭,缓存才会变为二级缓存 -
在测试二中,即使使用了二级缓存,如果readOnly参数为false,实体对象是实现了序列化接口了,那么结果是false。原因是,序列化是深拷贝。
-
要不要实现序列接口,由readOnly="true"参数决定。
官网的解释如下: readOnly(只读)属性可以被设置为 true 或 false。 1.(true)只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。 2.(false)而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些,但是更安全,因此默认值是 false。 其它博主的理解 1.当readOnly=true时 ,提示缓存此时应该只是用来读取数据 不要修改数据, 若A在此时修改了从缓存中提取出的数据会使得缓存中存取的数据也被修改,B在这时读取数据就会读取出被A修改后的错误数据。 2.当readOnly=false时, A修改了从缓存中提取出的数据并不会改变缓存中存储的原数据,B此时读取的就不会是错误的数据,这就确保了数据的安全。`
4、顺序
先看二级缓存有没有,没有再看一级缓存有没有。都没有就从数据库中查。
5、自定义缓存
在就可。具体用时再分析。其它可用如encache, 或者自己实现Cache接口。