MyBatis
学习:狂神
MyBatis
搭建环境
- 创建user表,添加数据
- 创建模块,导入坐标
- 编写M小yBatis核心配置文件->替换连接信息解决硬编码问题
- 编写SQL映射文件->统一管理sq语句,解决硬编码问题
- 编码
- 定义entity类
- 加载核心配置文件,获取SqlSessionFactory对象
- 获取SqlSession对象,执行SQL语句
- 释放资源
导jar包
- 核心包:mybatis
- 依赖包:
- commons-logging 日志包
- log4j 日志包
- 驱动包:mysql-connector-java
注册实体类
1 | public class User { |
注册配置文件
1 |
|
注册映射文件
1 |
|
测试类
1 |
|
增删改查
字符串替换 (类似于泛型 的 属性 )
- 替换前
1
2
3
4
5
6
7
8
9
10
User findById( long id);
User findByName( String name);
User findByEmail( String email);
// 其它的 "findByXxx" 方法- 优化后(方便简洁但不安全)
1
2
User findByColumn( String column, String value);sql片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<sql id="if-id-name">
<if test="id!=null">
id=#{id}
</if>
<if test="name!=null">
and name=#{name}
</if>
</sql>
<select id="findOne" resultMap="UserPwd">
select * from user
<where>
<include refid="if-id-name"></include>
</where>
</select>forEach
1
select * from `user` where id in (1,2,3);
1
2
3
4
5
6
7
8
9
10<select id="findForEach" resultMap="UserPwd" parameterType="map">
select * from user where id in
<foreach collection="items" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
// foreach: 遍历集合collection中的内容
// index : 下标 item:元素
// open,close : 开始,结束符
// separator : 自定义分割符( , or and ...)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void Test2() throws IOException {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map map=new HashMap<>();
ArrayList<Integer> items = new ArrayList<>();
items.add(1);
items.add(2);
items.add(3);
map.put("items",items);
List<User> userList = mapper.findForEach(map);
for (User user : userList) {
System.out.println(user);
}
}
配置解析
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
1、属性(properties)
可以直接引入外部文件
可以在其中增加一些属性配置
如果两个文件有同一个字段,优先使用外部配置文件的!
配置属性文件config.properties
1
2
3
4driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///mybatis?serverTimezone=GMT%2B8&useSSL=false
username=root
password=000000引入外部配置文件(如果两个文件有同一个字段,优先使用外部配置文件的!)
1
2
3
4
5<properties resource="config.properties"/>
<properties resource="config.properties">
<property name="password" value="优先读取属性文件"/>
</properties>1
2
3
4
5
6<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
2、设置(settings)
日志工厂
STDOUT_LOGGING
1
2
3
4<settings>
// mybatis 自带
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>log4j
1
2
3<settings>
<setting name="logImpl" value="log4j"/>
</settings>1
2
3
4
5
6
7
8
9
10
11
12
13public class MybatisTest03 {
//日志对象,参数为当前类的class (反射)
static Logger logger = Logger.getLogger(MybatisTest03.class);
//日志级别
public void log4jTest(){
logger.info("info->日志");
logger.debug("debug->日志");
logger.error("error->日志");
}
}
3、类型别名(typeAliases)
根据属性
1
2
3<typeAliases>
<typeAlias type="com.entity.User" alias="User"/>
</typeAliases>扫描实体类的包,它的默认别名就为这个类的类名,首字母小写!
1
2
3<typeAliases>
<package name="com.entity"/>
</typeAliases>在实体类中使用注解(优先级最高)
1
2
3
4
public class User {
....
}
4、其他配置
- 类型处理器(typeHandlers)
- 对象工厂(objectFactory)
- 插件(plugins)
- 数据库厂商标识
5、环境配置(environments)
环境配置
6、映射器(mappers)
1 | <!-- 方式一:使用相对于类路径的资源引用 --> |
1 | <!-- 方式二:使用映射器接口实现类的完全限定类名 --> |
1 | <!-- 方式三:将包内的映射器接口实现全部注册为映射器 --> |
使用方式二、三 时注意:
- 接口和他的Mapper配置文件必须同名!
- 接口和他的Mapper配置文件必须在同一个包下!
ResultMap
实体类中属性名与数据库中字段不一致(属性名字段名不一致解决方案)
1
2
3private int id;
private String name;
private String password; (数据库中为 pwd )- 数据库查询原理(可用起别名解决)
1
2
3
4<select id="findOne" resultType="User">
<!-- select * from user where id=#{id} -->
select id,name,pwd as password from user where id=#{id}
</select>ResultMap
1
2
3
4
5
6
7
8<!-- 结果集映射 column 数据库字段; property 实体类属性-->
<resultMap id="UserMap" type="user">
<result column="pwd" property="password"/>
</resultMap>
<select id="findOne" resultMap="UserMap">
select * from user where id=#{id}
</select>
结果映射(resultMap)
constructor —用于在实例化类时,注入结果到构造方法中
idArg- ID 参数;标记出作为 ID 的结果可以帮助提高整体性能arg- 将被注入到构造方法的一个普通结果
id–一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能result– 注入到字段或 JavaBean 属性的普通结果association —— 一个复杂类型的关联;许多结果将包装成这种类型
- 嵌套结果映射 – 关联可以是
resultMap元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
collection —— 一个复杂类型的集合
- 嵌套结果映射 – 集合可以是
resultMap元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
discriminator ——使用结果值来决定使用哪个 resultMap
case —— 基于某些值的结果映射
- 嵌套结果映射 –
case也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
分页
Limit 分页
mysql 代码
1 | select * from user limit #{startIndex},#{num} |
接口层
1 | public List<User> findOfLimit(Map<String,Integer> map); |
测试
1 | Map<String,Integer> map= new HashMap<>(); |
RowBounds 分页
mysql 代码
1 | select * from user; |
接口层
1 | public List<User> findByRowBounds(); |
测试层
1 | // RowBounds实现类 |
插件分页
注解开发
1 |
|
@Param()设置传入形参对接sql的限定名
复杂查询
多对一
Teacher
1
2private int id;
private String name;Student
1
2
3private int id;
private String name;
private Teacher teacher;association —n. 协会,联盟,社团;联合;联想
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<select id="findStu2" resultMap="StuMap">
select s.id sid,s.name sname,t.name tname // sql字段取别名,防止编译混淆
from student s ,teacher t
where s.tid =t.id ;
</select>
<resultMap id="StuMap" type="com.entity.Student"> //推荐使用类型别名,避免全限定名
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="com.entity.Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
<!-- ========================================================== -->
<select id="findStu" resultMap="StuTeacher">
select * from student;
</select>
<resultMap id="StuTeacher" type="com.entity.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<association property="teacher" column="tid" javaType="com.entity.Teacher" select="findTch"/>
</resultMap>
<select id="findTch" resultType="com.entity.Teacher"> // 根据tid查询老师
select * from teacher where id=#{tid}
</select>一对多
Student
1
2
3private int id;
private String name;
private int tid;Teacher
1
2
3private int id;
private String name;
private List<Student> student;collection
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<select id="findById" parameterType="_int" resultMap="TH">
select s.id sid,s.name sname,t.name tname ,t.id tid
from student s ,teacher t
where s.tid =t.id and t.id=#{id}
</select>
<resultMap id="TH" type="teacher">
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<!-- javaType :指定属性的返回值类型,
ofType : 集合中的泛型信息通常使用ofType获取-->
<collection property="student" javaType="ArrayList" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
<!-- =========================================================== -->
<select id="findById2" resultMap="TH2">
select * from teacher where id=#{id}
</select>
<resultMap id="TH2" type="teacher">
<collection property="student" javaType="ArrayList" ofType="student" select="findStu" column="id"/>
</resultMap>
<select id="findStu" resultType="student">
select * from student where tid=#{tid}
</select>
缓存
一级缓存 (本地缓存)是基于 PerpetualCache(MyBatis自带)的 HashMap 本地缓存,作用范围为 session 域内。当 session flush(刷新)或者 close(关闭)之后,该 session 中所有的 cache(缓存)就会被清空。
一级缓存失效的情况:
查询不同的东西
增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
查询不同的Mapper,Xml
手动清理缓存
1
sqlSession.clearCache();
1
2
3
4
5
6
7
8
9
10
11
12
public void test2(){
SqlSession sqlSession = sqlSessionFactory.openSession();
TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
TeacherMapper mapper1 = sqlSession.getMapper(TeacherMapper.class);
System.out.println(mapper.findById(1));
sqlSession.clearCache();
System.out.println(mapper1.findById(1));
sqlSession.close();
}
二级缓存 是全局缓存,作用域超出 session 范围之外,可以被所有 SqlSession 共享
配置
1
2
3
4
5<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
public void test(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
TeacherMapper mapper = sqlSession1.getMapper(TeacherMapper.class);
TeacherMapper mapper1 = sqlSession2.getMapper(TeacherMapper.class);
List<Teacher> teachers = mapper.findById(1);
for (Teacher teacher : teachers) {
System.out.println(teacher);
}
sqlSession1.close();
sqlSession1.clearCache(); //在二级缓存中无效
logger.info("----------------------");
List<Teacher> teachers1 = mapper1.findById(1);
for (Teacher teacher : teachers1) {
System.out.println(teacher);
}
System.out.println(teachers == teachers1);
sqlSession2.close();
}二级缓存遗留问题:为什么要openSession.close();后,才能从二级缓存中查数据,不是先执行二再一么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void TowCache(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
System.out.println(mapper1.findAll());
sqlSession1.close(); //在此处关闭,查询只执行了1遍
// sqlSession1.clearCache(); //对二级缓存无效
System.out.println(mapper2.findAll());
// sqlSession1.close(); //在此处关闭,查询执行了2遍
sqlSession2.close();
}
//注意:二级缓存需要openSession.close();后,才能从二级缓存中查数据解决:https://blog.csdn.net/sdfgegefdg/article/details/115741737
二级缓存需要openSession.close();后,才能从二级缓存中查数据
如果openSession.close();在第二次查询之后才关闭,则第二次查询会从一级缓存中查,如果不是一个session,则查询不到数据:
说明:第二次又重新发送了sql,因为从二级缓存中取数据时,会话没关闭所以二级缓存中没数据,所以又去一级缓存中查询,也没有数据则发送了sql查数据库;
所以,只有会话关闭或提交后,一级缓存中的数据才会转移到二级缓存中,然后因为是同一个namespace所以可以获取到数据;
Maven资源路径问题
1 | <build> |



