MyBatis 开发手册 (三)

简介: MyBatis 开发手册 (三)

玩转MyBatis多表关系#


xml版: 一对一级联#


实验场景: 比如说学生和学号是百分百的一对一的关系, 但是我直接用一个不太恰当的例子, 就是强制规定 用户和账户的是一对一的关系,即一个用户只能存在一个账户(这个例子很牵强...)


这时,我们想实现这样的级联效果: 在查询账户的同时,级联查询出这个账户所属的用户信息

第一点: 就是数据库中的表怎么设计,在账户表中添加一列当成外键,关联用户表中的用户id

第二点: 我们想在查询账户的同时级联查询出用户的信息,说白了就是让Mybatis帮我们将属性封装进我们使用resultType标签指定的返回值类型的对象中,于是我们就得像下面这样写 账户类,在账户类中添加用户信息引用


public class Account {
    private Integer id;
    private Integer uid;
    private Integer money;
    private User user;
}


第三点: sql语句怎么写?

关注点就是下面的resultMap标签中的<association>标签, 通过这个标签告诉Mybatis如何将查询出来的结果封装进Account中的User字段

可以看到我在association标签中将user所有的属性全都配置进去了,其实这是没必要的,因为我的sql语句并没有返回全部的结果


association中存在一个 column属性,这个属性存放就是在account表中的外键的列名 , javaType表示是告诉MyBatis,这个封装类的类型


<resultMap id="accountUserMap" type="com.changwu.pojo.Account">
    <id property="id" column="id"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <association property="user" column="uid" `javaType`="com.changwu.pojo.User">
     <id property="id" column="id"/>
       <result property="userName" column="userName"/>
       <result property="birthday" column="birthday"/>
       <result property="sex" column="sex"/>
      <result property="address" column="address"/>
    </association>
</resultMap>
  <!--查询所有的账户,同时包含用户名和地址信息-->
<select id="findAllAccount" resultMap="accountUserMap" >
    select a.*,u.username,u.address from account a,user u where a.uid=u.id
</select>


注解版: 一对一级联#


配置查询账户时,级联查询出账户所属的用户,如果说,账户实体和数据库中表的字段命名不同,需要用到下面的@Result()注解进行纠正,当然虽然我下面写了四个@Result,除了第一个配置id,中间两个的propertycolumn值是一样的,所以根本没有写的必要

有必要的是一对一的关系需要使用@Result()配置,同样column为Account表中的关联user表中的外键列名,强制不能写错,具体是一对一,但是一对多的关系通过one 和 manay控制, 通过fetchType控制是及早求值还是懒加载


@Select("select * from account")
@Results(id = "account",value = {
    //先描述自己的信息,然后描述一对一的信息
    @Result(id = true, property = "id",column = "id"),
    @Result(property = "uid",column = "uid"),
    @Result(property = "money",column = "money"),
    @Result(property = "user",column = "uid",
            one=@One(select = "com.changwu.dao.IUserDao.findById",fetchType = FetchType.EAGER))
})
List<Account> findAll();


XML版: 一对多级联#


实验场景: 查询用户的信息的同时级联查询出用户所有的账户的信息

在java中上面的描述转换成代码的意思是, User类中要添加一个集合,存放查询出来的账户的信息, 我们进一步通过配置告诉Mybatis将查询出的属性封装进这个list中


public class User {
    private Integer id;
    private String  userName;
    private Date    birthday;
    private String  sex;
    private String  address;
    // 在主表中唯一从表的集合
    private List<Account> accounts;


像下面这样配置

注意点就是在一对多的配置中我们使用collection标签,接着使用属性ofType标识 一的一方维护的集合中元素的类型


像这种property类型的属性全都是java类中的属性名,写错了MyBatis会报错

column属性: 按理说是数据库中列名,如果不一样的话,不至于报错,但是数据一定封装不上,但是有时候 也可能是在sql语句中为原列名取的别名的名称


<!-- todo 一对多的配置 -->
<resultMap id="findAllUserAndUserAccount" type="com.changwu.pojo.User">
    <id property="id" column="id"/>
    <result property="userName" column="userName"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>
    <!--一对多的配置-->
    <!--ofType是一的一方维护的集合中元素的类型-->
    <collection property="accounts"   ofType="com.changwu.pojo.Account" >
        <id property="id" column="aid"/>
        <result property="uid" column="uid"/>
        <result property="money" column="money"/>
    </collection>
</resultMap>
<select id="findAllUserAndUserAccount" resultMap="findAllUserAndUserAccount">
    select * from user u left  outer join account a on  u.id=a.uid
</select>


注解版: 一对多级联#


和1对1的配置很像


@Select("select * from user")
@Results(id="UserAccount",value = {
        @Result(id = true ,property = "id",column = "id"),
        @Result(property = "accounts",column = "id",
                many = @Many(select = "com.changwu.dao.IAccountDao.findAccountByUid",fetchType = FetchType.LAZY))
})
List<User> findAll();


多对多级联#


实验场景: 典型的用户和角色之间的关系

多对多的配置其实和一对多一样, 比如想在查询出用户信息时级联查询出这个用户拥有的角色的信息


于是第一步:

我们在User类中添加属性private List<Role> roles;


第二步: 写xml中的sql mapper配置

下面这个column属性配置的rid 其实就是在使用我们sql中为数据库中的某列取的别名

如果查询的结果中出现了两个相同的列名,但是值不同,代表的意义也不同,最好就给其中一个取别名


<!--todo 多对多 role user-->
<resultMap id="roleUserMap" type="com.changwu.pojo.Role">
    <!--todo 这里的colum就是不原生的数据库的列名,而是取的别名-->
    <id property="id" column="rid"/>
      <result property="roleName" column="role_name"/>
      <result property="roleDesc" column="role_desc"/>
    <collection property="users" ofType="com.changwu.pojo.User">
        <id property="id" column="id"/>
        <result property="userName" column="userName"/>
        <result property="birthday" column="birthday"/>
        <result property="sex" column="sex"/>
        <result property="address" column="address"/>
    </collection>
</resultMap>
<select id="findAll" resultMap="roleUserMap">
      select u.*,r.ID as rid,r.ROLE_NAME,r.ROLE_DESC from role r
          left join user_role ur on r.ID=ur.RID
          left join user u on ur.UID = u.id
</select>


MyBatis的延迟加载#


association一对一的延迟加载#


即用户和账户的关系是一对一的关系,我们希望这样,当用户仅仅查询账户信息时,Mybatis仅仅执行查询账户信息的语句,但是当用户使用这个账户关联的对象时,再让MyBatis将账户对象中的用户对象的引用时,触发懒加载,让mybatis再去查询数据库


像下面这样配置xml文件, 它其实是对原生的一对一的级联查询的升级,将association标签内部的通过result的属性描述全部去掉了,因为目标是懒加载,加上这些描述也用不到了


取而代之的是一个新星select, 它指向了IUserDao中的根据id查询用户的方法findUserById


还有一个注意点就是,association中的column属性不能去掉,而且必须写成数据库中Account表中存放关联User的外键的那个列名,通过它指定当触发延迟加载时,使用哪个字段给findById()方法使用


<!--todo 延迟加载-->
<resultMap id="findAllAccountLazy" type="com.changwu.pojo.Account">
    <id property="id" column="id"/>
    <result property="uid" column="uid"/>
    <result property="money" column="money"/>
    <!-- select指定的内容, 可以查询出单个用户的唯一方法标识 -->
    <!-- 这里的column属性,指定的是select中指定的fingById 所需要的id值-->
    <association property="user" column="uid" javaType="com.changwu.pojo.User" select="com.changwu.dao.IUserDao.findById">
    </association>
</resultMap>
 <select id="findAll" resultType="int" resultMap="findAllAccountLazy">
   select * from account
 </select>


下面是User中findById的配置, sql中的#{}中的内容是可以随便写的


<!--todo 疑问下面这id可不可以乱写 -->
<select id="findById" parameterType="int" resultMap="UserMap">
   select * from user where id = #{123id}
</select>


实验成功的结果就是,当我们使用Account的fingAll方法时,如果不继续getUser(),结果控制台打印单条sql,一旦使用getUser(),控制台会继续打印多条新的sql


collection实现一对多的延迟加载#


一个用户存在多个账户,我们希望如果仅仅是查询用户信息则延迟加载用户账户的信息,使用用户信息时,才再次执行新的sql加载用户的信息


实现的思路和上面的相似, 注意collection标签中的column的值,已经select标签中findAccountByUid的实现


<!-- todo 一对多的配置 延迟加载 -->
<resultMap id="findAllUserAndUserAccount" type="com.changwu.pojo.User">
    <id property="id" column="id"/>
    <result property="userName" column="userName"/>
    <result property="birthday" column="birthday"/>
    <result property="sex" column="sex"/>
    <result property="address" column="address"/>
    <!--一对多的配置-->
    <!--ofType是一的一方维护的集合中元素的类型-->
    <collection property="accounts" ofType="com.changwu.pojo.Account"
                 column="id" select="com.changwu.dao.IAccountDao.findAccountByUid">
    </collection>
</resultMap>
<select id="findAllUserAndUserAccount" resultMap="findAllUserAndUserAccount">
    select * from user
</select>


缓存#


什么是缓存?#


缓存是指将查询的数据暂存在内存中,当下次再次查询时,直接从内存中获取,实现减少和数据库交互的次数,提高执行效率


适用于缓存的数据: 经常被查询, 不经常被修改, 而且对此类数据的一致性没有很严格的要求, 与之相反的数据比如,银行的汇率,商品的库存中数据一致性要求极其严格的数据就不适合使用缓存机制


Mybatis中的一级缓存#


一级缓存是存在于MyBatis的SqlSession中的数据,当用户执行一次查询,查询的结果就会被缓存在SqlSession中一份,当用户再次查询时,先检查sqlSession中是否存在相应的数据,如果存在的话不再重新查询数据库,而是取出缓存中的数据给用户

所以,当sqlSession对象消失时,一级缓存就不复存在


一级缓存是默认存在的

像下面这个测试,全程使用同一个sqlSession,那么获取出来的user也会是同一个, 控制台仅仅输入一条sql,打印结果也为true


@Test
public void testFirstCache(){
    IUserDao userDao = this.sqlSession.getMapper(IUserDao.class);
    User user1 = userDao.findUserByIdFirstCache(42);
    User user2 =  userDao.findUserByIdFirstCache(42);
    System.out.println(user1==user2);
}


但是像下面这样,一级缓存将会消失


public void testFirstCache(){
    IUserDao userDao = this.sqlSession.getMapper(IUserDao.class);
    User user1 = userDao.findUserByIdFirstCache(42);
    this.sqlSession.close();
    this.sqlSession = this.factory.openSession();
       userDao = this.sqlSession.getMapper(IUserDao.class);
    User user2 =  userDao.findUserByIdFirstCache(42);
    System.out.println(user1==user2);
}


同样适用sqlSession的clearCache()也可以实现缓存的清空


为了安全,MyBatis的一级缓存在sqlSession出现 修改,添加,删除,commit(),close()等方法时,就会被清空


MyBatis中的二级缓存#


二级缓存指的是MyBatis中的SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession会共享这块二级缓存


使用: 在MyBatis主配置文件中开始支持缓存的配置,默认是开启的状态


<setting name="cacheEnabled" value="true"></setting>


在从配置文件中开启缓存的配置


<!--为当前的mapper开启二级缓存的支持-->
<cache/>


第三步: 在select标签中添加userCache属性


<!--测试一级缓存-->
<select id="findUserByIdFirstCache" parameterType="int" resultMap="UserMap" useCache="true">
   select * from user where id = #{id}
</select>


测试:

按照上面的配置后,编写下面的测试方法,测试二级缓存的存在就得关闭一级缓存,在下面的测试用例中同时开启两个sqlSession,第一个sqlSession查询完结果后随即关闭,接着开启第二个sqlSession,获取mapper继续查询,但是整个流程查询的sql仅仅会执行一次,原因就是存在二级缓存, 为什么最后的输出结果user!=user2呢? 因为属于SqlSessionFactory的二级缓存,存放的并不是对象,而是键值对形式存在的数据,使用这部分缓存时,MyBatis会自动为我们创新的对象,然后将这部分数据封装进去,返回这个新对象


@Test
public void testFirstCache(){
    SqlSession sqlSession1 = this.factory.openSession();
    IUserDao userDao = sqlSession1.getMapper(IUserDao.class);
    User user1 = userDao.findUserByIdFirstCache(42);
    System.out.println(user1);
    sqlSession1.close();
    SqlSession  sqlSession2 = this.factory.openSession();
    userDao =  sqlSession2.getMapper(IUserDao.class);
    User user2 =  userDao.findUserByIdFirstCache(42);
    System.out.println(user2);
    sqlSession2.close();
    System.out.println(user1==user2);
}


注解版开启二级缓存#


在我们的dao层上添加如下注解即可


@CacheNamespace(blocking = true)
相关文章
|
XML 存储 Java
神了!阿里P8纯手写出了这份10W字的MyBatis技术原理实战开发手册
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录
92 0
|
SQL XML Java
MyBatis 开发手册 (二)
MyBatis 开发手册 (二)
219 0
|
XML SQL 缓存
MyBatis 开发手册 (一)
这一遍看Mybatis的原因是怀念一下去年的 10月24号我写自己第一个项目时使用全配置文件版本的MyBatis,那时我们三个人刚刚大二,说实话,当时还是觉得MyBatis挺难玩的,但是今年再看最新版的Mybatis3.5.0, 还是挺有感觉的 Mybatis的官网一级棒...
308 0
|
1月前
|
Java 数据库连接 Maven
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和MyBatis Generator,使用逆向工程来自动生成Java代码,包括实体类、Mapper文件和Example文件,以提高开发效率。
105 2
mybatis使用一:springboot整合mybatis、mybatis generator,使用逆向工程生成java代码。
|
1月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
52 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
1月前
|
前端开发 Java Apache
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
本文详细讲解了如何整合Apache Shiro与Spring Boot项目,包括数据库准备、项目配置、实体类、Mapper、Service、Controller的创建和配置,以及Shiro的配置和使用。
288 1
Springboot整合shiro,带你学会shiro,入门级别教程,由浅入深,完整代码案例,各位项目想加这个模块的人也可以看这个,又或者不会mybatis-plus的也可以看这个
|
1月前
|
SQL Java 数据库连接
mybatis使用二:springboot 整合 mybatis,创建开发环境
这篇文章介绍了如何在SpringBoot项目中整合Mybatis和MybatisGenerator,包括添加依赖、配置数据源、修改启动主类、编写Java代码,以及使用Postman进行接口测试。
16 0
mybatis使用二:springboot 整合 mybatis,创建开发环境
|
1月前
|
Java 数据库连接 API
springBoot:后端解决跨域&Mybatis-Plus&SwaggerUI&代码生成器 (四)
本文介绍了后端解决跨域问题的方法及Mybatis-Plus的配置与使用。首先通过创建`CorsConfig`类并设置相关参数来实现跨域请求处理。接着,详细描述了如何引入Mybatis-Plus插件,包括配置`MybatisPlusConfig`类、定义Mapper接口以及Service层。此外,还展示了如何配置分页查询功能,并引入SwaggerUI进行API文档生成。最后,提供了代码生成器的配置示例,帮助快速生成项目所需的基础代码。
|
2月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
1月前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
42 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学