07 Mybatis的多表查询1----1对多和多对1---@Results注解用法总结
1.表与表之间的关系及其举例
表之间的关系有4种:一对多、多对一、一对一、多对多。
举例:
(1)用户和订单就是一对多
一个用户可以下多个订单
(2)订单和用户就是多对一
多个订单属于同一个用户
(3)人和身份证号就是一对一
一个人只能有一个身份证号
一个身份证号只能属于一个人
(4)老师和学生之间就是多对多
一个学生可以被多个老师教过
一个老师可以交多个学生
2.mybatis中的多表查询
示例:用户和账户
一个用户可以有多个账户
一个账户只能属于一个用户(多个账户也可以属于同一个用户)
步骤:
1、建立两张表:用户表,账户表
让用户表和账户表之间具备一对多的关系:需要使用外键在账户表中添加
2、建立两个实体类:用户实体类和账户实体类
让用户和账户的实体类能体现出来一对多的关系
3、建立两个配置文件
用户的配置文件
账户的配置文件
4、实现配置:
当我们查询用户时,可以同时得到用户下所包含的账户信息
当我们查询账户时,可以同时得到账户的所属用户信息
3.@Results注解用法总结:
MyBatis中使用@Results注解来映射查询结果集到实体类属性。
(1)@Results的基本用法。当数据库字段名与实体类对应的属性名不一致时,可以使用@Results映射来将其对应起来。column为数据库字段名,porperty为实体类属性名,jdbcType为数据库字段数据类型,id为是否为主键。
@Select({"select id, name, class_id from my_student"}) @Results({ @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="name", property="name", jdbcType=JdbcType.VARCHAR), @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER) }) List<Student> selectAll();
如上所示的数据库字段名class_id与实体类属性名classId,就通过这种方式建立了映射关系。
(2)@ResultMap的用法。当这段@Results代码需要在多个方法用到时,为了提高代码复用性,我们可以为这个@Results注解设置id,然后使用@ResultMap注解来复用这段代码。
@Select({"select id, name, class_id from my_student"}) @Results(id="studentMap", value={ @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER) }) List<Student> selectAll(); @Select({"select id, name, class_id from my_student where id = #{id}"}) @ResultMap(value="studentMap") Student selectById(integer id);
(3)@One的用法。当我们需要通过查询到的一个字段值作为参数,去执行另外一个方法来查询关联的内容,而且两者是一对一关系时,可以使用@One注解来便捷的实现。比如当我们需要查询学生信息以及其所属班级信息时,需要以查询到的class_id为参数,来执行ClassesMapper中的selectById方法,从而获得学生所属的班级信息。可以使用如下代码。
@Select({"select id, name, class_id from my_student"}) @Results(id="studentMap", value={ @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="class_id", property="myClass", javaType=MyClass.class, one=@One(select="com.example.demo.mapper.MyClassMapper.selectById")) }) List<Student> selectAllAndClassMsg();
(4)@Many的用法。与@One类似,只不过如果使用@One查询到的结果是多行,会抛出TooManyResultException异常,这种时候应该使用的是@Many注解,实现一对多的查询。比如在需要查询学生信息和每次考试的成绩信息时。
@Select({"select id, name, class_id from my_student"}) @Results(id="studentMap", value={ @Result(column="id", property="id", jdbcType=JdbcType.INTEGER, id=true), @Result(column="class_id", property="classId", jdbcType=JdbcType.INTEGER), @Result(column="id", property="gradeList", javaType=List.class, many=@Many(select="com.example.demo.mapper.GradeMapper.selectByStudentId")) }) List<Student> selectAllAndGrade();
参考文献:https://blog.csdn.net/cherlshall/article/details/80950150
4.操作案例
(1)案例1
查询所有查询所有账户,及其用户名和地址信息(逻辑为:账户表account-----》用户信息表user)
<1>对数据库表Account对应的实体类Account.java进行改造,加入User对象作为成员变量
package domain; import java.io.Serializable; /** * 数据库的account表对应的实体类 */ public class Account implements Serializable { private Integer id; private Integer uid; private Double money; //从表实体应该包含一个主表实体的对象引用 private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public Integer getUid() { return uid; } public void setUid(Integer uid) { this.uid = uid; } public Double getMoney() { return money; } public void setMoney(Double money) { this.money = money; } @Override public String toString() { return "Account{" + "id=" + id + ", uid=" + uid + ", money=" + money + '}'; } }
<2>IAccountDao中添加findAll()方法
package dao; import domain.Account; import domain.AccountUser; import java.util.List; public interface IAccountDao { /** * 查询所有查询所有账户,及其用户名和地址信息 * @return */ List<Account> findAll(); }
<3>映射关系配置
(1)IAccountDao.xml配置
ResultMap标签基本作用:建立SQL查询结果字段与实体属性的映射关系信息
标签属性id和type的含义:
id:该resultMap的标志
type:返回值的类名
子标签含义:
id:用于设置主键字段与领域模型属性的映射关系,此处主键为ID,对应id。
result:用于设置普通字段与领域模型属性的映射关系
association 为关联关系,是实现一对一的关键
1. property 为javabean中容器对应字段名
2. javaType 指定关联的类型,当使用select属性时,无需指定关联的类型
3. select 使用另一个select查询封装的结果
4. column 为数据库中的列名,与select配合使用
<?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="dao.IAccountDao"> <resultMap id="accountUserMap" type="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="domain.User"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> </association> </resultMap> <!-- 查询所有查询所有账户,及其用户名和地址信息:方法1(更通用) --> <!--account a 给account表起一个别名--> <select id="findAll" resultMap="accountUserMap"> select u.*,a.id as aid,a.uid,a.money from account a , user u where u.id = a.uid; </select> </mapper>
注解配置:IAccountDao.java文件中进行如下配置
/** * 查询所有查询所有账户,及其用户名和地址信息 * 注解中的第四个result的column为uid(外键),连接到user表,select调用dao.IUserDao.findById()方法 * @return */ @Select("select * from account") @Results(id="accountMap",value = { @Result(id=true,column = "id",property = "id"), @Result(column = "uid",property = "uid"), @Result(column = "money",property = "money"), @Result(column = "uid",property = "user",one=@One(select = "dao.IUserDao.findById",fetchType = FetchType.EAGER)) }) List<Account> findAll();
<4>测试代码
package test; import dao.IAccountDao; import dao.IUserDao; import domain.Account; import domain.AccountUser; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.InputStream; import java.util.List; public class MybatisTest01 { private InputStream in; private SqlSession sqlSession; private IAccountDao accountDao; /** * 初始化MyBatis * @throws Exception */ public void initMyBatis() throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder(); //创建SqlSessionFactory的构建者builder SqlSessionFactory factory=builder.build(in); //利用构建者builder创建SqlSessionFactory //3.使用工厂生产SqlSession对象 sqlSession = factory.openSession(); //4.使用SqlSessions对象创建Dao接口的代理对象 accountDao = sqlSession.getMapper(IAccountDao.class); } /** * 释放资源 * @throws Exception */ public void destroy() throws Exception{ sqlSession.commit();//提交事务 sqlSession.close(); in.close(); } /** * 查询所有 * @throws Exception */ @Test public void testFindAll()throws Exception{ initMyBatis(); List<Account> accounts = accountDao.findAll(); for (Account account : accounts) { System.out.println(account); System.out.println(account.getUser()); } destroy(); } }
效果图:
(2)案例2
查询所有查询用户信息,及其拥有的账户信息(逻辑为:用户信息表user-----》账户表account)
<1>对数据库表user对应的实体类User.java进行改造,加入List<Account>作为成员变量
package domain; import java.io.Serializable; import java.util.Date; import java.util.List; /** * 数据库表对应的实体类 */ public class User implements Serializable { //实体类的成员变量名称应该与数据库中的列名一致 private Integer id; private String username; private Date birthday; private String sex; private String address; private List<Account> accounts; public List<Account> getAccounts() { return accounts; } public void setAccounts(List<Account> accounts) { this.accounts = accounts; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + ", birthday=" + birthday + ", sex='" + sex + '\'' + ", address='" + address + '\'' + '}'; } }
<2>IUserDao中添加findAll()方法
package dao; import domain.User; import java.util.List; /** * */ public interface IUserDao { /** * 查询所有 * @return */ List<User> findAll(); }
<3>IUserDao.xml
ResultMap标签基本作用:建立SQL查询结果字段与实体属性的映射关系信息
标签属性id和type的含义:
id:该resultMap的标志
type:返回值的类名
子标签含义:
id:用于设置主键字段与领域模型属性的映射关系,此处主键为ID,对应id。
result:用于设置普通字段与领域模型属性的映射关系
association 为关联关系,是实现一对一的关键
1. property 为javabean中容器对应字段名
2. javaType 指定关联的类型,当使用select属性时,无需指定关联的类型
3. select 使用另一个select查询封装的结果
4. column 为数据库中的列名
collection 为关联关系,是实现一对多的关键
1. property 为javabean中容器对应字段名
2. ofType 指定集合中元素的对象类型
3. select 使用另一个查询封装的结果
4. column 为数据库中的列名,与select配合使用
<?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="dao.IUserDao"> <resultMap id="userAccountMap" type="domain.User"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> <!--配置user对象中accounts集合的映射--> <collection property="accounts" ofType="domain.Account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> </collection> </resultMap> <!-- 查询所有 --> <select id="findAll" resultMap="userAccountMap"> select * from user u left outer join account a on u.id = a.uid </select> </mapper>
注解配置:IUserDao.java文件中进行如下配置
/** * 查询所有 * @return */ @Select("select * from user") @Results(id="userMap",value = { @Result(id=true,column = "id",property = "id"), @Result(column = "username",property = "username"), @Result(column = "address",property = "address"), @Result(column = "sex",property = "sex"), @Result(column = "birthday",property = "birthday"), @Result(column = "id",property ="accounts",many =@Many(select = "dao.IAccountDao.findAccountById",fetchType = FetchType.EAGER)) }) List<User> findAll();
<4>测试代码
package test; import dao.IAccountDao; import dao.IUserDao; import domain.Account; import domain.AccountUser; import domain.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.InputStream; import java.util.List; public class MybatisTest02_User { private InputStream in; private SqlSession sqlSession; private IUserDao userDao; /** * 初始化MyBatis * @throws Exception */ public void initMyBatis() throws Exception{ //1.读取配置文件 in = Resources.getResourceAsStream("SqlMapConfig.xml"); //2.创建SqlSessionFactory SqlSessionFactoryBuilder builder=new SqlSessionFactoryBuilder(); //创建SqlSessionFactory的构建者builder SqlSessionFactory factory=builder.build(in); //利用构建者builder创建SqlSessionFactory //3.使用工厂生产SqlSession对象 sqlSession = factory.openSession(); //4.使用SqlSessions对象创建Dao接口的代理对象 userDao = sqlSession.getMapper(IUserDao.class); } /** * 释放资源 * @throws Exception */ public void destroy() throws Exception{ sqlSession.commit();//提交事务 sqlSession.close(); in.close(); } /** * 查询所有 * @throws Exception */ @Test public void testFindAll()throws Exception{ initMyBatis(); List<User> users = userDao.findAll(); for (User user : users) { System.out.println(user); System.out.println(user.getAccounts()); } destroy(); } }
效果图:
4.Mybatis中的缓存
(1)什么是缓存
存在于内存中的临时数据。
(2)为什么使用缓存
减少和数据库的交互次数,提高执行效率。
(3)什么样的数据能使用缓存,什么样的数据不能使用
<1>适用于缓存:
经常查询并且不经常改变的。
数据的正确与否对最终结果影响不大的。
<2>不适用于缓存:
经常改变的数据
数据的正确与否对最终结果影响很大的。
例如:商品的库存,银行的汇率,股市的牌价。
5.Mybatis中的一级缓存和二级缓存
(1)一级缓存:
它指的是Mybatis中SqlSession对象的缓存。
当我们执行查询之后,查询的结果会同时存入到SqlSession为我们提供一块区域中。
该区域的结构是一个Map。当我们再次查询同样的数据,mybatis会先去sqlsession中
查询是否有,有的话直接拿出来用。
当SqlSession对象消失时,mybatis的一级缓存也就消失了。
(2)二级缓存:
它指的是Mybatis中SqlSessionFactory对象的缓存。由同一个SqlSessionFactory对象创建的SqlSession共享其缓存。
二级缓存的使用步骤:
第一步:让Mybatis框架支持二级缓存(在SqlMapConfig.xml中配置)
第二步:让当前的映射文件支持二级缓存(在IUserDao.xml中配置)
第三步:让当前的操作支持二级缓存(在select标签中配置)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)