一 关联映射
1.1 关联映射关系
在关系型数据库中,表与表之间存在着三种关联映射关系,分别为一对一关系、一对多关系和多对多关系。
- 一对一关系 一个数据表中的一条记录最多可以和另一个数据表中的一条记录相关
- 一对多关系 主键数据表中的一条记录可以和另外一个数据表的多条记录相关。但另外一个数据表中的记录只能与主键数据表中的某一条记录相关。
- 多对多关系 一个数据表中的一条记录可以与另外一个数据表任意数量的记录相关,另外一个数据表中的一条记录也可以与本数据表中任意数量的记录相关。
1.2 Java对象如何描述事物之间的关系
数据表之间的关系实质上描述的是数据之间的关系,除了数据表,在Java中,还可以通过对象来描述数据之间的关系。通过Java对象描述数据之间的关系,其实就是使对象的属性与另一个对象的属性相互关联,Java对象描述数据之间的关联映射关系有三种,分别是一对一、一对多和多对多。
- 一对一 在本类中定义与之关联的类的对象作为属性。
- 一对多 一个A类对象对应多个B类对象的情况。
- 多对多 在两个相互关联的类中,都可以定义多个与之关联的类的对象。
二 一对一查询
在MyBatis中,通过<association>
元素来处理一对一关联关系。<association>
元素提供了一系列属性用于维护数据表之间的关系,该元素有如下属性:
属性 | 说明 |
---|---|
property | 用于指定映射到的实体类对象的属性,与表字段一一对应 |
column | 用于指定表中对应的字段 |
javaType | 用于指定映射到实体对象的属性的类型 |
jdbcType | 用于指定数据表中对应字段的类型 |
fetchType | 用于指定在关联查询时是否启用延迟加载。fetchType属性有lazy和eager两个属性值,默认值为lazy |
select | 用于指定引入嵌套查询的子SQL语句 |
autoMapping | 用于指定是否自动映射 |
typeHandler | 用于指定一个类型处理器 |
<association>
元素是<resultMap>
元素的子元素,它有两种配置方式,嵌套查询方式和嵌套结果方式,其配置方法如下:
- 嵌套查询方式 嵌套查询是指通过执行另外一条SQL映射语句来返回预期的复杂类型。
- 嵌套结果方式 嵌套结果是使用嵌套结果映射来处理重复的联合结果的子集。
三 一对多查询
与一对一的关联关系相比,接触更多的关联关系是一对多(或多对一),在MyBatis中,通过<collection>
元素来处理一对多关联关系。<collection>
元素的属性大部分与<association>
元素相同,但其还包含一个特殊属性 – ofType。ofType属性与javaType属性对应,它用于指定实体类对象中集合类属性所包含的元素的类型。
<collection>
元素是<resultMap>
元素的子元素,<collection>
元素有嵌套查询和嵌套结果两种配置方式。
- 嵌套查询方式 嵌套查询是指通过执行另外一条SQL映射语句来返回预期的复杂类型。
- 嵌套结果方式 嵌套结果是使用嵌套结果映射来处理重复的联合结果的子集。
四 多对多查询
在数据库中,多对多的关联关系通常使用一个中间表来维护,其他说明与第三节一致,不做赘述。
五 Mybites缓存机制
5.1 一级缓存
MyBatis的一级缓存是SqlSession级别的缓存。如果同一个SqlSession对象多次执行完全相同的SQL语句时,在第一次执行完成后,MyBatis会将查询结果写入到一级缓存中,此后,如果程序没有执行插入、更新、删除操作,当第二次执行相同的查询语句时,MyBatis会直接读取一级缓存中的数据,而不用再去数据库查询,从而提高了数据库的查询效率。
例如,存在数据表tb_book,从表中多次查询id为1的图书信息,当程序第一次查询id为1的图书信息时,程序会将查询结果写入MyBatis一级缓存,当程序第二次查询id为1的图书信息时,MyBatis直接从一级缓存中读取,不再访问数据库进行查询。当程序对数据库执行了插入、更新、删除操作,MyBatis会清空一级缓存中的内容以防止程序误读。
MyBatis如何防止程序误读
当程序对数据库执行了插入、更新、删除操作后,MyBatis会清空一级缓存中的内容,以防止程序误读。MyBatis一级缓存被清空之后,再次使用SQL查询语句访问数据库时,MyBatis会重新访问数据库。例如上面的例子,首先查询id为1的图书信息,然后使用更新语句对数据库中的图书信息进行更改,更改之后,再次对id为1的图书信息进行查询时,MyBatis依然会从数据库中查询。
5.2 二级缓存
-
优点:由5.1节的内容可知,相同的Mapper类,相同的SQL语句,如果SqlSession不同,则两个SqlSession查询数据库时,会查询数据库两次,这样也会降低数据库的查询效率。为了解决这个问题,就需要用到MyBatis的二级缓存。MyBatis的二级缓存是Mapper级别的缓存,与一级缓存相比,二级缓存的范围更大,多个SqlSession可以共用二级缓存,并且二级缓存可以自定义缓存资源。
-
执行过程 :在MyBatis中,一个Mapper.xml文件通常称为一个Mapper,MyBatis以namespace区分Mapper,如果多个SqlSession对象使用同一个Mapper的相同查询语句去操作数据库,在第一个SqlSession对象执行完后,MyBatis会将查询结果写入二级缓存,此后,如果程序没有执行插入、更新、删除操作,当第二个SqlSession对象执行相同的查询语句时,MyBatis会直接读取二级缓存中的数据。
-
默认状态下,开启二级缓存可实现以下功能:
- 映射文件中所有select语句将会被缓存。
- 映射文件中的所有insert、update和delete语句都会刷新缓存。
- 缓存会使用LRU算法回收。
- 没有刷新间隔,缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象的1024个引用。
- 缓存是可读/可写的缓存,这意味着对象检索不是共享的,缓存可以安全的被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
与MyBatis的一级缓存不同的是,MyBatis的二级缓存需要手动开启:
<!-- 开启当前Mapper的namespace下的二级缓存--> <cache> </cache>
-
元素的属性
属性 说明 flushInterval 刷新间隔。该属性可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况下是不设置值。 size 引用数目。该属性可以被设置为任意正整数,默认值为1024。 readOnly 只读。该属性可以被设置为true或者false。当缓存设置为只读时,缓存对象不能被修改,但此时缓存性能较高。当缓存设置为可读写时,性能较低,但安全性高。 eviction 回收策略。该属性有4个可选值。
LRU:最近最少使用的策略。移除最长时间不被使用的对象。
FIFO:先进先出策略。按对象进入缓存的顺序来移除它们。
SOFT:软引用策略。移除基于垃圾回收器状态和软引用规则的对象。
WEAK:弱引用策略。更积极地移除基于垃圾收集器状态和弱引用规则的对象.
Cache Hit Ratio(缓存命中率)
终端用户访问缓存时,如果在缓存中查找到了要被访问的数据,就叫做命中。如果缓存中没有查找到要被访问的数据,就是没有命中。当多次执行查询操作时,缓存命中次数与总的查询次数(缓存命中次数+缓存没有命中次数)的比,就叫作缓存命中率,即缓存命中率=缓存命中次数/总的查询次数。当MyBatis开启二级缓存后,第一次查询数据时,由于数据还没有进入缓存,所以需要在数据库中查询而不是在缓存中查询,此时,缓存命中率为0。第一次查询过后,MyBatis会将查询到的数据写入缓存中,当第二次再查询相同的数据时,MyBatis会直接从缓存中获取这条数据,缓存将命中,此时的缓存命中率为0.5(1/2)。当第三次查询相同的数据,则缓存命中率为0.66666(2/3),以此类推。
五 相关代码
本章代码修改了前面几章的部分代码,此处仅放出本章相关代码,你可以自己核对代码或者在github下载代码查看。
5.1 SQL
USE d_mybiteslearn;
CREATE TABLE tb_idcard
(
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自增 ID
code VARCHAR(18) -- 身份证号码,长度为18
);
CREATE TABLE tb_person
(
id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,自增 ID
name VARCHAR(20), -- 姓名,长度为20
age INT, -- 年龄
sex VARCHAR(10), -- 性别,长度为10
card_id INT, -- 外键,关联 tb_idcard 表的 id
FOREIGN KEY (card_id) REFERENCES tb_idcard (id) -- 设置外键关联
);
CREATE TABLE tb_user
(
id int(32) PRIMARY KEY AUTO_INCREMENT,
username varchar(32),
address varchar(256)
);
CREATE TABLE tb_orders
(
id INT PRIMARY KEY AUTO_INCREMENT, -- 订单ID
number VARCHAR(50) NOT NULL, -- 订单编号
user_id INT, -- 用户外键,关联到 tb_users 表
FOREIGN KEY (user_id) REFERENCES tb_user (id) -- 外键约束
);
CREATE TABLE tb_product
(
id INT(32) PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(32),
price DOUBLE
);
create Table tb_ordersitem
(
id int primary key auto_increment,
orders_id int,
product_id int
);
INSERT INTO tb_user
VALUES ('1', '小明', '北京');
INSERT INTO tb_user
VALUES ('2', '小1明', '北2京');
INSERT INTO tb_user
VALUES ('3', '小3明', '北3京');
INSERT INTO tb_idcard (code)
VALUES ('510821196605224121'),
('510821200502118521');
INSERT INTO tb_person (name, age, sex, card_id)
VALUES ('zhangsan', 18, 'male', 1), -- card_id 关联 tb_idcard 表中的第 1 条记录
('lisi', 19, 'female', 2); -- card_id 关联 tb_idcard 表中的第 2 条记录
INSERT INTO tb_orders (number, user_id)
VALUES ('ORD001', 1),
('ORD002', 1),
('ORD003', 2),
('ORD004', 2),
('ORD005', 2),
('ORD006', 3);
INSERT INTO tb_product
VALUES ('1', 'Java基础入门', 44.5),
('2', 'JavaEE', 43.5),
('3', 'Java基础入门2', 44.5);
INSERT INTO tb_ordersitem (orders_id, product_id)
VALUES ('1', '1'),
('1', '2'),
('2', '1'),
('2', '2');
5.2 Dao
5.2.1 src/main/java/site/icefox/javaeelearn/Dao/OrdersDao.java
package site.icefox.javaeelearn.Dao;
import org.apache.ibatis.session.SqlSession;
import site.icefox.javaeelearn.Entity.OrdersEntity;
import site.icefox.javaeelearn.Mapper.OrdersMapper;
import site.icefox.javaeelearn.Util.DBConn;
public class OrdersDao {
public static OrdersEntity findOrdersWithPorduct(int id) {
OrdersEntity result = null;
try (SqlSession session = DBConn.getSqlSession()) {
OrdersMapper ordersMapper = session.getMapper(OrdersMapper.class);
result = ordersMapper.findOrdersWithPorduct(id);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return result;
}
}
5.2.2 src/main/java/site/icefox/javaeelearn/Dao/PersonDao.java
package site.icefox.javaeelearn.Dao;
import org.apache.ibatis.session.SqlSession;
import site.icefox.javaeelearn.Entity.PersonEntity;
import site.icefox.javaeelearn.Mapper.PersonMapper;
import site.icefox.javaeelearn.Util.DBConn;
public class PersonDao {
public static PersonEntity findPersonById(Integer id) {
PersonEntity person = null;
try (SqlSession session = DBConn.getSqlSession()) {
PersonMapper mapper = session.getMapper(PersonMapper.class);
person = mapper.findPersonById(1);
} catch (Exception e) {
System.out.println(e.getMessage());
}
return person;
}
}
5.2.3 src/main/java/site/icefox/javaeelearn/Dao/PersonDao.java
package site.icefox.javaeelearn.Dao;
import org.apache.ibatis.session.SqlSession;
import site.icefox.javaeelearn.Entity.UserEntity;
import site.icefox.javaeelearn.Entity.UsersEntity;
import site.icefox.javaeelearn.Mapper.UserMapper;
import site.icefox.javaeelearn.Mapper.UsersMapper;
import site.icefox.javaeelearn.Util.DBConn;
import java.util.List;
public class UserDao {
// 此处省略前文代码
public static UsersEntity findUserWithOrders(Integer id) {
UsersEntity result = null;
try (SqlSession session = DBConn.getSqlSession()) {
UsersMapper usersmapper = session.getMapper(UsersMapper.class);
// 2.查询id为1的用户信息
result = usersmapper.findUserWithOrders(id);
} catch (Exception e) {
System.out.println(e.getMessage());
}
// 3.输出查询结果信息
return result;
}
}
5.3 Entity
位于src/main/java/site/icefox/javaeelearn/Entity/
5.3.1 IdCardEntity.java
package site.icefox.javaeelearn.Entity;
import lombok.Data;
@Data
public class IdCardEntity {
private Integer id; // 主键id
private String code; // 身份证号码
}
5.3.2 OrdersEntity.java
package site.icefox.javaeelearn.Entity;
import lombok.Data;
import java.util.List;
@Data
public class OrdersEntity {
private Integer id; //订单id
private String number; //订单编号
private List<ProductEntity> productList;
}
5.3.3 ProductEntity.java
package site.icefox.javaeelearn.Entity;
import lombok.Data;
@Data
public class ProductEntity {
private Integer id;
private String name;
private Double price;
}
5.3.4 UsersEntity.java
package site.icefox.javaeelearn.Entity;
import lombok.Data;
import java.util.List;
@Data
public class UsersEntity {
private Integer id; // 用户编号
private String username; // 用户姓名
private String address; // 用户地址
private List<OrdersEntity> ordersList; // 用户关联的订单
}
5.4 Mapper
位于site/icefox/javaeelearn/Mapper
下
5.4.1 IdCardMapper.java
package site.icefox.javaeelearn.Mapper;
import site.icefox.javaeelearn.Entity.IdCardEntity;
public interface IdCardMapper {
IdCardEntity findCodeById(Integer id);
}
5.4.2 OrdersMapper.java
package site.icefox.javaeelearn.Mapper;
import site.icefox.javaeelearn.Entity.OrdersEntity;
public interface OrdersMapper {
OrdersEntity findOrdersWithPorduct(int id);
}
5.4.3 PruductMapper.java
package site.icefox.javaeelearn.Mapper;
import site.icefox.javaeelearn.Entity.ProductEntity;
public interface ProductMapper {
ProductEntity findProductById(int id);
}
5.4.4 UsersMapper.java
package site.icefox.javaeelearn.Mapper;
import site.icefox.javaeelearn.Entity.UsersEntity;
public interface UsersMapper {
UsersEntity findUserWithOrders(Integer id);
}
5.5 XML
位于src/main/resources/site/icefox/javaeelearn/Mapper/
5.5.1 IdCardMapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="site.icefox.javaeelearn.Mapper.IdCardMapper">
<!-- 二 一对一查询 -->
<!-- 根据id查询证件信息 -->
<select id="findCodeById" parameterType="Integer" resultType="IdCardEntity">
SELECT *
FROM tb_idcard
WHERE id = #{id}
</select>
</mapper>
5.5.2 OrdersMapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="site.icefox.javaeelearn.Mapper.OrdersMapper">
<select id="findOrdersWithPorduct" parameterType="Integer"
resultMap="OrdersWithProductResult">
select *
from tb_orders
WHERE id = #{id}
</select>
<resultMap type="OrdersEntity" id="OrdersWithProductResult">
<id property="id" column="id"/>
<result property="number" column="number"/>
<collection property="productList" column="id" ofType="ProductEntity"
select="site.icefox.javaeelearn.Mapper.ProductMapper.findProductById">
</collection>`
</resultMap>
</mapper>
5.5.3 ProductMapper.xml
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="site.icefox.javaeelearn.Mapper.ProductMapper">
<select id="findProductById" parameterType="Integer"
resultType="ProductEntity">
SELECT *
from tb_product
where id IN (SELECT product_id
FROM tb_ordersitem
WHERE orders_id = #{id})
</select>
</mapper>
5.5.4 UsersMapper.xml
<?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">
<!--namespace=绑定一个对应的Dao/Mapper接口-->
<mapper namespace="site.icefox.javaeelearn.Mapper.UsersMapper">
<!-- 三 一对多查询-->
<resultMap type="UsersEntity" id="UserWithOrdersResult">
<id property="id" column="user_id"/> <!-- 用户ID -->
<result property="username" column="username"/> <!-- 用户名 -->
<result property="address" column="address"/> <!-- 用户地址 -->
<collection property="ordersList" ofType="OrdersEntity">
<id property="id" column="orders_id"/> <!-- 订单ID -->
<result property="number" column="number"/> <!-- 订单号 -->
</collection>`
</resultMap>
<select id="findUserWithOrders" resultMap="UserWithOrdersResult">
SELECT *
FROM tb_user u
LEFT JOIN tb_orders o ON u.id = o.user_id
WHERE u.id = #{id}
</select>
</mapper>
5.6 src/test/java/v4_CodeTest.java
import org.junit.Test;
import site.icefox.javaeelearn.Dao.OrdersDao;
import site.icefox.javaeelearn.Dao.PersonDao;
import site.icefox.javaeelearn.Dao.UserDao;
public class v4_CodeTest {
// 2 一对一查询
@Test
public void findPersonByIdTest() {
System.out.println(PersonDao.findPersonById(1));
}
// 3 一对多
@Test
public void findUserWithOrdersTest() {
System.out.println(UserDao.findUserWithOrders(1));
}
// 4 多对多
@Test
public void findOrdersTest() {
System.out.println(OrdersDao.findOrdersWithPorduct(1));
}
}