大家好。我是一位大学在校生,在校期间学习了Mybatis框架,认为学习完一门东西,一定要做一些笔记好重复去体会实现的过程,于是花了几天时间整理了Mybatis框架知识。有参考书籍、教学视频、文章做统一的整理,文章挺长的,两万字左右。建议先收藏后面有空慢慢看,一起加油吧。
目录
3、关于mapper.xml中的占位符"#{}"和"${}"
一、概述
1、首先Mybatis属于框架,那么我们需要先了解什么是框架?
框架是一个半成品软件,将所有的公共的,重复的功能解决掉,帮助程序快速高效的进行开发。它是可复用、可扩展的。
2、什么是Mybatis框架?
MyBatis 本是 apache 的一个开源项目iBatis, 2010 年这个项目由 apache software foundation 迁移到了google code,并且改名为 MyBatis。它是一款优秀的、半自动的ORM持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。下图即是Mybatis的架构原理(蓝色圆柱体为数据库)。
3、什么是ORM?
ORM(Object/Relational Mapping)即对象关系映射,是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系,并且提供一种机制,通过JavaBean对象去操作数据库表的数据。 MyBatis通过简单的XML或者注解的方式进行配置和原始映射,将实体类和SQL语句之间建立映射关系,是一种半自动(之所以说是半自动,因为我们要自己写SQL)的ORM实现。
4、什么是持久化?
数据持久化是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。例如,文件的存储、数据的读取等都是数据持久化操作。数据模型可以是任何数据结构或对象的模型、XML、二进制流等。 当我们编写应用程序操作数据库,对表数据进行增删改查的操作的时候就是数据持久化的操作。
二、初学者快速入门
1、创建项目,选择Maven,从archetype中创建。
注意:普通的Java统一创建为:org.apache.maven.archetypes:maven-archetype-quickstart
而如果是Web项目的话统一为:org.apache.maven.archetypes:maven-archetype-webapp
2、就是在创建好后的pom.xml中添加以下依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--添加Mybatis框架的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--添加MySQL框架的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
</dependencies>
<!--添加资源文件的指定-->
<!--记得在加入资源文件后再右上角的Maven中刷新一遍-->
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.xml</include>
<include>**/*.properties</include>
</includes>
</resource>
</resources>
</build>
3、目录结构如图。在resources中去创建一个jdbc.properties的属性文件(数据库的配置)
4、编写全局配置信息,resources下添加SqlMapConfig.xml文件(Mybatis的核心配置文件)
<?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>
<!--第一步:先读取jdbc.properties属性文件-->
<properties resource="jdbc.properties" />
<!--第二步:设置日志输出-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--第三步:注册实体类的别名-->
<typeAliases>
<package name="com.chf.pojo"/>
</typeAliases>
<!--第四步:配置环境变量-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--第五步:注册mapper.xml文件-->
<mappers>
<!--批量注册-->
<package name="com.chf.mapper" />
</mappers>
</configuration>
5、创建实体类Users类,用来封装数据。
package com.chf.pojo;
import java.util.Date;
//以下省略无参有参构造、重写toString方法、所有属性的Set及Get方法
public class Users {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
}
6、在mapper下创建UsersMapper接口,定义方法。
package com.chf.mapper;
import com.chf.pojo.Users;
public interface UsersMapper {
List<Users> getAll();//查询所有用户的信息
}
7、接着继续在mapper目录下创建UsersMapper.xml文件,用来编写Sql
<?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">
<!--绑定dao接口,面向接口编程-->
<mapper namespace="com.chf.mapper.UsersMapper">
<select id="getAll" resultType="users">
select id,username,birthday,sex,address
from users
</select>
</mapper>
8、紧接着在测试类Test中的java下创建MyTest类,在@Test左边即可完成运行。
package com.chf;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyTest01 {
@Test
public void testGetAll() throws IOException {
//使用文件流读取核心配置文件SqlMapConfig.xml
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
//创建SqlSessionFactory工厂(框架初始化)
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
//取出SqlSession的对象
SqlSession sqlSession = factory.openSession();
//完成查询操作
List<Users> list = sqlSession.selectList("users.getAll");
list.forEach(users -> System.out.println(users));
//关闭SqlSession
sqlSession.close();
}
}
三、深入了解Mybatis框架
1、关于全局配置文件SqlMapConfig.xml
其<configuration></configuration>中,中间的标签是用严格的顺序的,否则无法执行/执行报错。
到源码的mybatis-3-config-dtd中,我们可以发现有这么一条语句,即是对标签的顺序做出的要求:
<!ELEMENT configuration (properties?, settings?, typeAliases?, typeHandlers?,
objectFactory?, objectWrapperFactory?, reflectorFactory?,
plugins?, environments?, databaseIdProvider?, mappers?)>
//由于太长我就不截图了,当做代码块放出来给大家看
以下只介绍学习过程中所使用到的一些标签
configuration(配置)
properties(属性):实现引用配置文件
settings(设置):日志形式输出到控制台
typeAliases(类型别名):用来减少类完全限定名的冗余
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置):用来配置数据源
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器):用来配置mapper.xml映射文件且这些xml文件里都是SQL语句
1.1、属性<properties>
其用处就是用来读取我们所需要的配置文件的信息的。比如jdbc.properties,就不多介绍了。
1.2、设置<setting>
当设置了将输出到控制台上,我们就可以多读取到这些信息,还可以查看到Sql语句是否有误。
1.3、类型别名<typeAliases>,用法主要有两种:
- 在实体类比较少的时候,使用第一种方式。
- 如果实体类十分多,建议使用第二种方式。
1.4、环境配置<environment>
注意的是在这里面使用的是"${}"占位符
<!--配置数据库的环境变量(数据库连接配置)
default:使用下面的environment标签的id属性进行指定配置
-->
<environments default="development">
<!--id:就是提供给environments的default属性使用-->
<environment id="development">
<!--配置事务管理器
type:指定事务管理的方式
JDBC:事务的控制交给程序员处理
MANAGED:由容器(Spring)来管理事务
-->
<transactionManager type="JDBC"/>
<!--配置数据源
type:指定不同的配置方式
JNDI:java命名目录接口,在服务器端进行数据库连接池的管理
POOLED:使用数据库连接池
UNPOOLED:不使用数据库连接池
-->
<dataSource type="POOLED">
<!--配置数据库连接的基本参数-->
<property name="driver" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
1.5、映射器<mappers>
mappers中有四种注册方式,选择一种即可,优先级分别是package > resource > url > class。
<!--注册mapper.xml文件-->
<mappers>
<!--动态代理方式下的单个mapper.xml文件注册-->
<mapper class="com.chf.mapper.UsersMapper"></mapper>
<!--绝对路径注册-->
<mapper url="/"></mapper>
<!--非动态代理方式下的注册-->
<mapper resource="StudentMapper.xml"></mapper>
<!--批量注册-->
<package name="com.chf.mapper"></package>
</mappers>
2、关于数据库访问层接口和映射文件(CRUD)
2.1、Select、Insert、Update、Delete
①根据数据库中的Users表去pojo包下写关于Users的实体类。实体类代码在上面的快速入门中。
②然后在mapper目录下接口中定义想要对数据库操作的方法
public interface UsersMapper {
List<Users> getAll();//查询所有用户的信息
int update(Users user);//用户的更新
int insert(Users user);//新增用户
int delete(Integer id);//删除用户
}
③相同目录下绑定该接口,创建mapper文件。
其中的namespace中的包名要和mapper接口的包名保持一致。
id:就是对应的namespace中的方法名;
resultType:Sql语句执行的返回值
parameterType:传入测试类中的参数类型
注意:在这里使用的是"#{}"占位符,在后面我会讲解${}和#{}两者的区别和用法
<mapper namespace="com.chf.mapper.UsersMapper">
<select id="getAll" resultType="users">
select id,username,birthday,sex,address
from users
</select>
<insert id="insert" parameterType="users">
insert into users (username,birthday,sex,address)
values(#{username},#{birthday},#{sex},#{address})
</insert>
<update id="update" parameterType="users">
update users set username=#{username},birthday=#{birthday},
sex=#{sex},address=#{address}
</update>
<delete id="delete" parameterType="users">
delete from users
where id=#{id}
</delete>
</mapper>
④在test包下创建一个测试类去完成对数据库访问的测试
注意:只要我们进行完增删改操作后一定还要有提交的操作即commit。
public class MyTest0 {
@Test
public void testGetAll() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
List<Users> list = sqlSession.selectList("users.getAll");
list.forEach(user -> System.out.println(users));
sqlSession.close();
}
@Test
public void testInsert() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
sqlSession.insert("users.insert","王小八",new Date(),"2","福建省");
sqlSession.commit();
sqlSession.close();
}
@Test
public void testUpdate() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
sqlSession.update("users.update",7,"王五",new Date(),"2","泉州市");
sqlSession.commit();
sqlSession.close();
}
@Test
public void testDelete() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
sqlSession.delete("users.delete",6);
sqlSession.commit();
sqlSession.close();
}
}
当你看到这里时,你会发现测试类的代码重复写肯定是很不正常的,于是就会引入注解来对其测试进行优化代码的重复量。
@Before:在执行Test前一定会先执行的操作
@After:在执行完Test后一定会执行的操作
public class MyTest{
SqlSession sqlSession;
@Before
public void openSqlSession() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
sqlSession = factory.openSession();
}
@After
public void closeSqlSession(){
sqlSession.close();
}
@Test
public void testGetAll(){
List<Users> list = sqlSession.selectList("users.getAll");
list.forEach(user -> System.out.println(users));
}
@Test
public void testInsert(){
sqlSession.insert("users.insert","王小八",new Date(),"2","福建省");
sqlSession.commit();
}
@Test
public void testUpdate(){
sqlSession.update("users.update",7,"王五",new Date(),"2","泉州市");
sqlSession.commit();
}
@Test
public void testDelete(){
sqlSession.update("users.delete",6);
sqlSession.commit();
}
}
看到这里,有没有想到一个问题:当接口内的方法变多了的话,测试类中每次测试方法都需要写上"resultType.方法",如果方法名改变的话或者新增方法的话,改变起来会很复杂。这并不符合程序扩展的OCP原则。于是我们就需要采用动态代理:即使接口中的业务发生了改变 此处也不需要做改变。我们需要进一步的对上面的测试类进行改变。
2.2、动态代理的规范
1)UsersMapper.xml文件与UsersMapper.java的接口必须同一个目录下.
2)UsersMapper.xml文件与UsersMapper.java的接口的文件名必须一致,后缀不管.
3)UserMapper.xml文件中标签的id值与与UserMapper.java的接口中方法的名称完全一致.
4)UserMapper.xml文件中标签的parameterType属性值与与UserMapper.java的接口中方法的参数类型完全一致.
5)UserMapper.xml文件中标签的resultType值与与UserMapper.java的接口中方法的返回值类型完全一致.
6)UserMapper.xml文件中namespace属性必须是接口的完全限定名com.chf.mapper.UsersMapper
7)在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名com.chf.mapper.UsersMapper.
public class MyTest{
SqlSession sqlSession;
UsersMapper usersMapper;//动态代理对象
@Before
public void openSqlSession() throws IOException {
InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
sqlSession = factory.openSession();
//取出动态代理的对象,完成接口中方法的调用,实则是调用xml文件中相应的标签的功能
usersMapper = sqlSession.getMapper(UsersMapper.class);
}
@After
public void closeSqlSession(){
sqlSession.close();
}
@Test
public void testGetAll(){
List<Users> list = usersMapper.getAll();
list.forEach(user -> System.out.println(users));
}
@Test
public void testInsert(){
Users users = new Users("王小八",new Date(),"2","福建省");
usersMapper.insert(users);
sqlSession.commit();
}
@Test
public void testUpdate(){
Users users = new Users(7,"王五",new Date(),"2","泉州市");
usersMapper.update(users);
sqlSession.commit();
}
@Test
public void testDelete(){
usersMapper.delete(8);
sqlSession.commit();
}
}
3、关于mapper.xml中的占位符"#{}"和"${}"
1、#{}占位符是为了获取值,通常在where语句后。
2、若在Sql代码中语句为select * from users where id=#{id},执行后自动翻译为select * from users where id=?。这就相当于JDBC中的PreparedStatement预编译阶段,可以有效的防止Sql注入。
3、#{}里如何写,看parameterType参数的类型。如果parameterType的类型是简单类型(8种基本(封装)+String),则#{}里随便写;如果是实体类的类型,则#{}里只能是类中成员变量的名称,而且区分大小写。
4、${}字符串用于拼接或字符串替换。一般用于模糊查询中,建议少用,因为有Sql注入的风险。同样分两种情况:如果parameterType的类型是简单类型,则${}里随便写,但是分版本,如果是3.5.1及以下的版本,只能写value;如果是实体类的类型,则${}里只能是类中成员变量的名称(现在已经少用)。
4、模糊查询以及主键
前面我们看过"#{}"和"${}"的区别,所以我们就能知道为了防止Sql注入的风险,就需要使用到"#{}"的占位符,而不需要使用"${}"占位符。
<select id="getByName" parameterType="string" resultType="users">
select id,username,birthday,sex,address
from users
where username like concat('%',#{username},'%')
</select>
@Test
public void testGetByName(){
List<Users> list = usersMapper.getByName("小");
list.forEach(users -> System.out.println(users));
}
当我们需要查询不止一项的时候,就需要写好几个模糊查询的语句,这样代码量就很大,于是需要将模糊查询进行优化,变成高级模糊查询。这里我们需要在接口中定义方法的时候对其形参属性使用注解@Param。这个注解是为了SQL语句赋值参数而服务的。
<!--
高级模糊查询:模糊查询多个的话,parameterType就不需要写
List<Users> getByNameOrAdd(
@Param("columnName")
String columnName,
@Param("columnValue")
String columnValue);
-->
<select id="getByNameOrAdd" resultType="users">
select id,username,birthday,sex,address
from users
where ${columnName} like concat('%',#{columnValue},'%')
</select>
@Test
public void testGetByNameOrAdd(){
List<Users> list1 = usersMapper.getByNameOrAdd("username","小");
List<Users> list2 = usersMapper.getByNameOrAdd("address","市");
list1.forEach(users -> System.out.println(users));
System.out.println("===========================================");
list2.forEach(users -> System.out.println(users));
}
通常我们会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,那么在插入一条记录后,如何得到数据库自动生成的这条记录的主键id呢? 使用<selectKey>子标签
<insert id="insert" parameterType="users">
<selectKey keyProperty="id" resultType="int" order="AFTER">
select last_insert_id()
</selectKey>
insert into users (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
返回主键值,插入语句结束后,返回自增的主键值传到入参的users对象的id属性中
keyProperty:users对象的哪个属性来接返回的属性值
resultType:返回的主键的类型
order:Before是在插入语句前执行,After是在插入语句后执行上面的last_insert_id()实际上是MySQL提供的一个函数,可以用来获取最近插入或更新的记录的主键id。
5、动态SQL
我们需要了解什么是动态SQL:
可以定义代码片段,可以进行逻辑判断,可以进行循环处理(批量处理),使条件判断更为简单.
①<sql>:用来定义代码片段,可以将所有的列名,或复杂的条件定义为代码片段,供使用时调用.
②<include>:用来引用<sql>定义的代码片段.③<if>:进行条件判断
test条件判断的取值可以是实体类的成员变量,可以是map的key,可以是@Param注解的名称。④<where>:进行多条件拼接,在查询,删除,更新中使用.
⑤<set>:有选择的进行更新处理,至少更新一列.能够保证如果没有传值进来,则数据库中的数据保持不变.
⑥<foreach>:用来进行循环遍历,完成循环条件查询,批量删除,批量增加,批量更新.
<mapper namespace="com.chf.mapper.UsersMapper">
<!--定义代码片段-->
<sql id="Columns">
id,username,birthday,sex,address
</sql>
<select id="getAll" resultType="users">
select <include refid="Columns" />
from users
</select>
<!--按指定的条件进行多条件查询-->
<select id="getByCondition" parameterType="users" resultType="users">
select <include refid="Columns" />
from users
<where>
<if test="username != null and username != ''">
and username like concat('%',#{username},'%')
</if>
<if test="birthday != null">
and birthday=#{birthday}
</if>
<if test="sex != null and sex != ''">
and sex=#{sex}
</if>
<if test="address != null and address != ''">
and address like concat('%',#{address},'%')
</if>
</where>
</select>
<select id="getByIds" resultType="users">
select <include refid="Columns" />
from users
where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</select>
<update id="update" parameterType="users">
update users set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address}
</update>
<update id="updateBySet" parameterType="users">
update users
<set>
<if test="username != null and username != ''">
username=#{username},
</if>
<if test="birthday != null">
birthday=#{birthday},
</if>
<if test="sex != null and sex != ''">
sex=#{sex},
</if>
<if test="address != null and address != ''">
address=#{address}
</if>
</set>
where id=#{id}
</update>
<!--批量增加-->
<insert id="insertBatch">
insert into users(username,birthday,sex,address) values
<foreach collection="list" item="u" separator=",">
(#{u.username},#{u.birthday},#{u.sex},#{u.address})
</foreach>
</insert>
<delete id="delete" parameterType="users">
delete from users
where id=#{id}
</delete>
<!--批量删除-->
<delete id="deleteBatch">
delete from users
where id in
<foreach collection="array" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
其中<foreach>标签的东西比较多,就一一进行说明一下:
collection:用来指定入参的类型即paramterType,如果是List集合则为list,如果是Map集合则为map,如果是数组则是array
item:每次循环遍历出来的值或者对象,随便取
separator:多个值之间的分隔符
open:在<foreach>前面
close:在</foreach>后面
6、指定参数的位置
如果入参是多个,可以通过指定参数位置进行传参.,是实体包含不住的条件。实体类只能封装住成员变量的条件。如果某个成员变量要有区间范围内的判断,或者有两个值进行处理,则实体类包不住。
<!--
查询指定日期范围内的用户:List<Users> getByBirthday(Date begin,Date end);
-->
<select id="getByBirthday" resultType="users">
select <include refid="Columns" />
from users
where birthday between #{arg0} and #{arg1}
</select>
6.1、入参为Map
如果入参超过一个以上,使用map封装查询条件,更有语义,查询条件更明确。
<!--入参是Map:List<Users> getByMap(Map map)
#{birthdayBegin}和#{birthdayEnd}:就是map中的key
-->
<select id="getByMap" resultType="users">
select <include refid="Columns" />
from users
where birthday between #{birthdayBegin} and #{birthdayEnd}
</select>
测试类中
@Test
public void testGetByMap() throws ParseException {
Map map = new HashMap<>();
map.put("birthdayBegin",new Date());
map.put("birthdayEnd", new Date());
List<Users> list = usersMapper.getByMap(map);
list.forEach(users -> System.out.println(users));
}
6.2、返回值为Map
如果返回的数据实体类无法包含,可以使用map返回多张表中的若干数据。返回后这些数据之间没有任何关系,就是Object类型,返回的map的key就是列名或别名。
<!--
返回值是一行的map:Map getReturnMap(Integer id);
-->
<select id="getReturnMap" parameterType="int" resultType="map">
select username nam,address a
from users
where id=#{id}
</select>
<!--
返回多行的map:List<Map> getMulMap();
-->
<select id="getMulMap" resultType="map">
select username,address
from users
</select>
7、表与表之间的关联关系
关联关系是有方向的。以下我只展示一对多和多对一的代码。
①一对多关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在老师方,就是一对多关联。
②多对一关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在学生方,就是多对一关联。
③一对一关联:一个老师辅导一个学生,一个学生只请教一个老师,学生和老师是一对一。
④多对多关联:园区划线的车位和园区的每一辆车,任意一个车位可以停任意一辆车,任意一车辆车可以停在任意一个车位上。
以下是一对多的实例
public class Customer {
private Integer id;
private String name;
private Integer age;
//该客户名下所有订单的信息
private List<Orders> ordersList;
//以下我还是省略有参无参构造、所有属性的setget方法、重写toString方法
}
<mapper namespace="com.chf.mapper.CustomerMapper">
<resultMap id="customermap" type="customer">
<id property="id" column="cid" />
<result property="name" column="name" />
<result property="age" column="age" />
<!--collection是用于一对多的关系-->
<collection property="ordersList" ofType="orders">
<id property="id" column="oid" />
<result property="orderNumber" column="orderNumber" />
<result property="orderPrice" column="orderPrice" />
</collection>
</resultMap>
<select id="getById" parameterType="int" resultMap="customermap">
select c.id cid,name,age,o.id oid,orderNumber,orderPrice
from customer c
left join orders o
on c.id=o.customer_id
where c.id=#{id}
</select>
</mapper>
@Test
public void testGetAll(){
List<Book> list = bookMapper.getAll();
list.forEach(book -> System.out.println(book));
}
以下是多对一的实例
public class Orders {
private Integer id;
private String orderNumber;
private Double orderPrice;
//关联此订单的客户信息
private Customer customer;
//以下依旧省略所有属性的setget方法、重写toString方法、有参无参构造方法
}
<mapper namespace="com.chf.mapper.OrdersMapper">
<!--
Orders getById(Integer id);
private Integer id;
private String orderNumber;
private Double orderPrice;
private Customer customer;
-->
<resultMap id="ordersmap" type="orders">
<id property="id" column="oid" />
<result property="orderNumber" column="orderNumber" />
<result property="orderPrice" column="orderPrice" />
<!--association是用于一对一或者多对一的情况-->
<association property="customer" javaType="customer">
<id property="id" column="cid" />
<result property="name" column="name" />
<result property="age" column="age" />
</association>
</resultMap>
<select id="getByIdTwo" parameterType="int" resultMap="ordersmap">
select o.id oid,orderNumber,orderPrice,customer_id,c.id cid,name,age
from orders o
inner join customer c
on c.id=o.customer_id
where o.id=#{id}
</select>
</mapper>
@Test
public void testGetByIdTwo(){
Orders orders = ordersMapper.getByIdTwo(11);
System.out.println(orders);
}
总结:无论什么关联关系,如果某方持有另一方的集合(一对多),则使用<collection>标签完成映射;如果某方持有另一方的对象(多对一或者一对一),则使用<association>标签完成映射。
四、缓存机制
首先我们需要知道:MyBatis框架提供两级缓存,一级缓存和二级缓存,并且默认开启一级缓存,缓存就是为了提交查询效率。 使用缓存后,查询的流程为:查询时先到缓存里查,如果没有则查询数据库,再放缓存一份,再返回客户端。下次再查询的时候直接从缓存返回,不再访问数据库,如果数据库中发生commit()操作,则清空缓存。
一级缓存使用的是SqlSession的作用域,同一个sqlSession共享一级缓存的数据。
1、如果在mapper映射文件中的CRUD标签中加入flushCache="true",会导致一二级缓存机制都消失
2、也可以在测试类中直接手动清理缓存。
3、也可以在全局配置文件设置<settings name="localCacheScope" value="STATEMENT" />这样会使一级缓存失效,二级缓存不受影响。
1、
2、
3、
二级缓存也叫做全局缓存,基于namespace级别的缓存。使用的是mapper的作用域,不同的sqlSession只要访问的同一个mapper.xml文件,则共享二级缓存作用域。需要在全局配置文件中设置<setting name="cacheEnabled" value="true" />,然后在具体的mapper.xml中添加<cache />即可。
以上就是我在学习Mybatis中的笔记了,谢谢大家观看。