https://www.bilibili.com/video/BV1wy4y1H7wu

在这里插入图片描述

为什么需要Mybatis

因为传统的JDBC代码很麻烦,我们就有了框架把它简化一下

  • 简单易学
  • 解除sql与程序代码的耦合
  • 提供xml标签
  • 提供映射标签
  • 使用的人多

为什么是Mybatis

Mybatis优点:

  • 学习成本低,上手快,当学完JDBC之后,再去学习Mybatis,上手时间要比Hibernate快。

  • SQL优化方便,MyBatis可以进行更为细致的SQL优化,可以减少查询字段。

Hibernate优点:

  • Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
  • Hibernate学习成本很高,但是它功能强大,数据库无关性好,还有O/R映射能力很强。
  • Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
  • Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
  • Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。

第一个Mybatis程序

文件目录

在这里插入图片描述

搭建环境、导入依赖

创建maven项目,删除src文件夹,导入依赖

<!-- mybatis依赖 -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>
<!-- lombok依赖 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
</dependency>

添加配置文件

我们需要在src下的resources文件夹下创建名为mybatis-config.xml的文件,然后根据官方提示,在文件中写入如下配置文件,在xml语法中,&需要转义,即&amp;

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="422518"/>
            </dataSource>
        </environment>
    </environments>
    <!--记得最后把Mapper配置文件放进来-->
    <mappers>
        <mapper resource="com/dao/UserMapper.xml"/>
    </mappers>
</configuration>

Spring Boot的相关配置,dao层的接口加上@mapper注解

spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=422518

创建实体类

@Data
public class User {
    String username;
    String password;
}

编写dao接口

public interface UserDao {
    List<User> findAll();
}

mapper接口实现类

我们需要建立xml配置文件,告诉SqlSession 和 Mapper 具体执行什么操作,配置文件可以写在任何地方,一般放在resources文件夹下,和接口位置同名的项目下(见文件目录)

注意,命名空间为要绑定的Dao(Mapper)接口,其中id对应的方法名,可以右键copy reference

<?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="com.dao.UserDao">
    <select id="findAll" resultType="com.pojo.User">
        select * from test.user
    </select>
</mapper>

进行测试

import com.utils.MybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.List;
import com.pojo.User

public class UserDaoTest {
    @Test
    //获得sqlSession
    //使用Mybatis第一步,获取SqlSessionFactory对象
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //sqlSession可以理解为数据库连接
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //执行sqSession
    UserDao mapper = sqlSession.getMapper(UserDao.class);//获取对象
    List<User> userList = mapper.findAll();//执行对象的方法
    //userList.for即可得到这一部分代码
    for (User user : userList) {
        System.out.println(user.toString());
    }
    sqlSession.close();
}

使用插件

插件名:Free Mybatis plugin

在dao文件类名、方法名使用alt+enter可以自动生成mapper文件,支持xml和方法的跳转

数据库的增删改查

如果返回一个对象就使用类作为返回类型,这个返回值包含所有的信息,如果是字符串或者数字就指定类型,如果返回值是List只需要写原对象即可。

如果通过传入的id没找到就会返回null,传入的id不存在也会返回null

<select id="getUserById" resultType="com.demo.entity.User" parameterType="String">
    select * from user where account=#{id}
</select>

注意:增删改需要提交事务,即调用close之前先commit

使用万能的Map进行添加

接口

int addUser2(Map<String,String> map);

mapper

<!--    user_account和user_pwd是map的key-->
<insert id="addUser" parameterType="Map">
    insert into user (account,pwd) values (#{user_account},#{user_pwd});
</insert>

方法有多个参数时

我以为使用#{paramName}即可高枕无忧,但是学到了方法有多个参数的时候,是用这种形式会报错,这时候应该在方法中进行操作,否则会变成avg1,avg2等等

User findByCondition(@Param("id") Integer id, @Param("username") String username);

配置文件

properties(从properties文件读取)

https://mybatis.org/mybatis-3/zh/configuration.html#properties

使用properties标签读取xml内容

settings(一些属性设置)

https://mybatis.org/mybatis-3/zh/configuration.html#settings

<!-- 开启驼峰命名 -->
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"></setting>
</settings>

typeAliases(类型别名)

类的别名

<typeAliases>
    <typeAlias type="com.demo.pojo.User" alias="User"/>
</typeAliases>

包的别名

<typeAliases>
    <typeAlias package="com.demo.pojo"/>
</typeAliases>

environments(修改操作环境)

https://mybatis.org/mybatis-3/zh/configuration.html#environments

mappers

使用相对于类路径的资源引用

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>

将包内的映射器接口实现全部注册为映射器

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

注解开发

直接将SQL写在dao层的接口方法上即可,但不灵活,不是很常用,记得在配置文件中写接口的包

public interface UserDao {
    @Select("select * from user where id=#{id}")
    User findById(Integer id);
}

动态sql

<if>

满足test的判断条件之后才会拼接sql,注意,test中如果需要使用方法参数,不需要加#{}

<select id="findActiveBlogLike" resultType="Blog">
    SELECT * FROM BLOG WHERE state = ‘ACTIVE’
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
</select>

<trim>

清除或添加前缀或后缀,多种情况用|分割,不要加空格,用的比较少

属性含义
prefixOverrides删除标签内前缀
suffixOverrides删除标签内后缀
prefix如果标签内有内容则添加前缀
suffix如果标签内有内容则添加后缀

综合案例:

<select id="findActiveBlogLike" resultType="User">
    select * from user
    <trim prefix="where" prefixOverrides="and|or">
        <if test="id!=null">
            id=#{id}
        </if>
        <!--id为null,username不为null的时候,可以清除and-->
        <if test="username!=null">
            and username=#{username}
        </if>
    </trim>
</select>

<where>

用的蛮多的,where标签等价于

<trim prefix="where" prefixOverrides="and|or"></trim>

使用案例

<select id="findActiveBlogLike" resultType="User">
    select * from user
    <where>
        <if test="id!=null">
            id=#{id}
        </if>
        <!--id为null,username不为null的时候,可以清除and-->
        <if test="username!=null">
            and username=#{username}
        </if>
    </where>
</select>

如果id和username都为null,则执行的sql为:**select * from user **

如果id为null,username不为null,则执行的sql为:**select * from user where username = ? **

<set>

当更新的时候,如果某些字段为null,则会直接设置为null,但实际我们希望应该这些字段不更新,这时候使用set标签就很方便了,可以使用set在sql拼接set字段,同时去掉后缀逗号

set标签等价于

<trim prefix="set" suffixOverrides=","></trim>

使用案例

<update id="updateById">
    update user
    <set>
        <if test="username != null">
            username=#{username},
        </if>
        <if test="age != null">
            age=#{age},
        </if>
        <if test="address != null">
            address=#{address},
        </if>
    </set>
    where id=#{id}
</update>

如果调用方法时传入的User对象的id为2,username不为null,其他属性都为null则最终执行的sql为:UPDATE USER SET username = ? where id = ?

<foreach>

当我们查询的参数是一个数组的时候,我们期望动态的根据实际传入的数组的长度拼接SQL语句。例如传入长度为4个数组最终执行的SQL为:

select * from User where id in( ? , ? , ? , ?, ? ) 

例如方法定义如下

List<User> findByIds(@Param("ids") Integer[] ids);

则在xml映射文件中可以使用以下写法

<select id="findByIds" resultType="com.sangeng.pojo.User">
    select * from User
    <where>
        <foreach collection="ids" open="id in(" close=")" item="id" separator=",">
            #{id}
        </foreach>
    </where>
</select>
  • collection:表示要遍历的参数
  • open:表示遍历开始时拼接的语句
  • item:表示给当前遍历到的元素的取的名字
  • separator:表示每遍历完一次拼接的分隔符
  • close:表示最后一次遍历完拼接的语句

注意:如果方法参数是数组类型,默认的参数名是array,如果方法参数是list集合默认的参数名是list。建议遇到数组或者集合类型的参数统一使用@Param注解进行命名。

choose、when、otherwise

三者配套使用,when、otherwise都是放在choose标签里的,类似switch语法

使用场景如下:

  • 如果user对象的id不为空时就通过id查询。
  • 如果id为null,username不为null就通过username查询。
  • 如果id和username都会null就查询id为3的用户
<select id="selectChose" resultType="com.sangeng.pojo.User">
    select * from user
    <where>
        <choose>
            <when test="id!=null">
                id = #{id}
            </when>
            <when test="username!=null">
                username = #{username}
            </when>
            <otherwise>
                id = 3
            </otherwise>
        </choose>
    </where>
</select>
  • choose类似于java中的switch

  • when类似于java中的case

  • otherwise类似于java中的dufault

​ 一个choose标签中最多只会有一个when中的判断成立。从上到下去进行判断。如果成立了就把标签体的内容拼接到sql中,并且不会进行其它when的判断和拼接。如果所有的when都不成立则拼接otherwise中的语句。

SQL片段单独抽取

重复的片段可以单独抽取出来,提高复用性和简洁度

<sql id="baseSelect" >id,username,age,address</sql>
<select id="findAll" resultType="com.sangeng.pojo.User">
    select <include refid="baseSelect"/>  from user
</select>

resultMap的使用(自定义映射关系)

基本使用:

<!--
    resultMap 用来自定义结果集和实体类的映射
        属性:
            id 相当于这个resultMap的唯一标识
            type 用来指定映射到哪个实体类
    id标签  用来指定主键列的映射规则
        属性:
            property 要映射的对象属性名
            column  对应的列名
    result标签 用来指定普通列的映射规则
        属性:
            property 要映射的属性名
            column 对应的列名
-->
<resultMap id="orderMap" type="com.sangeng.pojo.Order" >
    <id column="id" property="id"></id>
    <result column="createtime" property="createtime"></result>
    <result column="price" property="price"></result>
    <result column="remark" property="remark"></result>
    <result column="user_id" property="userId"></result>
</resultMap>

<!--使用我们自定义的映射规则-->
<select id="findAll" resultMap="orderMap">
    SELECT id,createtime,price,remark,user_id  FROM ORDERS
</select>

开启自动映射(只自定义映射某几个,相同的就不用设置了):

<!--默认情况下自动映射是打开的,可以在resultMap设置属性autoMapping关闭-->
<resultMap id="orderMap" type="com.sangeng.pojo.Order" >
    <result column="user_id" property="userId"></result>
</resultMap>
<!--使用我们自定义的映射规则-->
<select id="findAll" resultMap="orderMap">
    SELECT id,createtime,price,remark,user_id  FROM ORDERS
</select>

继承映射关系:

<!--定义个父映射,供其他resultMap继承-->
<resultMap id="baseOrderMap" type="com.sangeng.pojo.Order" >
    <id column="id" property="id"></id>
    <result column="createtime" property="createtime"></result>
    <result column="price" property="price"></result>
    <result column="remark" property="remark"></result>
</resultMap>
<!--继承baseOrderMap,然后只需要写自己特有的映射关系即可-->
<resultMap id="orderMap" type="com.sangeng.pojo.Order" autoMapping="false" extends="baseOrderMap">
    <result column="user_id" property="userId"></result>
</resultMap>

多表查询

实现多表查询大概有两种方式,一是写个多表查询的SQL,二是分步查询

但是在自动映射的时候可能没有合适的实体类做映射,我们可以使用resultMap做映射

关联查询

一对一(某个对象封装到属性)

方式一:使用ResultMap对所有字段进行映射

<!--Order和User关联的映射-->
<resultMap id="orderUserMap" type="com.sangeng.pojo.Order" autoMapping="false" extends="orderMap">
    <id column="id" property="id"></id>
    <result column="createtime" property="createtime"></result>
    <result column="price" property="price"></result>
    <result column="remark" property="remark"></result>
    <result column="uid" property="user.id"></result>
    <result column="username" property="user.username"></result>
    <result column="age" property="user.age"></result>
    <result column="address" property="user.address"></result>
</resultMap>

方式二:使用ResultMap中的association设置关联实体类的映射规则.

<resultMap id="baseOrderMap" type="com.sangeng.pojo.Order" >
    <id column="id" property="id"></id>
    <result column="createtime" property="createtime"></result>
    <result column="price" property="price"></result>
    <result column="remark" property="remark"></result>
</resultMap>

<resultMap id="orderMap" type="com.sangeng.pojo.Order" autoMapping="false" extends="baseOrderMap">
    <result column="user_id" property="userId"></result>
</resultMap>

<!--Order和User关联的映射(使用association)-->
<resultMap id="orderUserMapUseAssociation" type="com.sangeng.pojo.Order" autoMapping="false" extends="orderMap">
    <!--property是Order对象的user属性-->
    <association property="user" javaType="com.sangeng.pojo.User">
        <id property="id" column="uid"></id>
        <result property="username" column="username"></result>
        <result property="age" column="age"></result>
        <result property="address" column="address"></result>
    </association>
</resultMap>

一对多(某个对象集封装到属性)

因为期望User中还能包含该用户所具有的角色信息,所以可以在User中增加一个属性

//该用户所具有的角色
private List<Role> roles;

SQL语句如下

SELECT 
	u.`id`,u.`username`,u.`age`,u.`address`,r.id rid,r.name,r.desc
FROM 
	USER u,user_role ur,role r
WHERE 
	u.id=ur.user_id AND ur.role_id = r.id
	AND u.id = 2

结果集

在这里插入图片描述

使用resultMap的collection

<!--定义User基本属性映射规则-->
<resultMap id="userMap" type="com.sangeng.pojo.User">
    <id column="id" property="id"></id>
    <result column="username" property="username"></result>
    <result column="age" property="age"></result>
    <result column="address" property="address"></result>
</resultMap>

<resultMap id="userRoleMap" type="com.sangeng.pojo.User"  extends="userMap">
    <!--集合-->
    <collection property="roles" ofType="com.sangeng.pojo.Role" >
        <id column="rid" property="id"></id>
        <result column="name" property="name"></result>
        <result column="desc" property="desc"></result>
    </collection>
</resultMap>

分步查询(嘶,有点麻烦哦)

场景:还是一对多的用户-角色的查询

  1. 首先查user
  2. 根据user.id查询角色信息
  3. 写两个方法,再写两个SQL即可
  4. 配置分步查询

分步查询配置

我们期望的效果是调用findByUsername方法查询出来的结果中就包含角色的信息。所以我们可以设置findByUsername方法的RestltMap,指定分步查询

<resultMap id="userMap" type="com.sangeng.pojo.User">
    <id property="id" column="id"></id>
    <result property="username" column="username"></result>
    <result property="age" column="age"></result>
    <result property="address" column="address"></result>
</resultMap>
<!--
    select属性:指定用哪个查询来查询当前属性的数据 写法:包名.接口名.方法名
    column属性:设置当前结果集中哪列的数据作为select属性指定的查询方法需要参数
-->
<resultMap id="userRoleMapBySelect" type="com.sangeng.pojo.User" extends="userMap">
    <collection property="roles"
                ofType="com.sangeng.pojo.Role"
                select="com.sangeng.dao.RoleDao.findRoleByUserId"
                column="id">
    <!--这里的column="id"是结果集返回的查询的参数-->
    </collection>
</resultMap>

​ 指定findByUsername使用我们刚刚创建的resultMap

<!--根据用户名查询用户-->
<select id="findByUsername" resultMap="userRoleMapBySelect">
    select id,username,age,address from user where username = #{username}
</select>

分步查询设置按需加载

我们可以设置按需加载,这样在我们代码中需要用到关联数据的时候才会去查询关联数据,例如我们只想要用户信息,不想要角色信息。xml设置之后,当我们只需要user.id的时候,系统会智能的不去进行角色信息的查询

1、方式一:局部配置

设置resultMapfetchType属性为lazy

<resultMap id="userRoleMapBySelect" type="com.sangeng.pojo.User" extends="userMap">
    <collection property="roles"
                ofType="com.sangeng.pojo.Role"
                select="com.sangeng.dao.RoleDao.findRoleByUserId"
                column="id" fetchType="lazy">
    </collection>
</resultMap>

2、方式二:全局配置

mybatis-config.xml里设置lazyLoadingEnabledtrue

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

如何选择

分布式项目中分步查询可能会更好点,因为多表关联查询可能会很耗时,同时分步查询能进行按需加载,如果是小项目用关联查询也是OK的

而且在使用PageHelper的时候多表查询一对多查询,会出现关联数据不全的情况

分页查询——PageHelper

引入依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.0.0</version>
</dependency>

配置Mybatis使用分页插件

<plugins>
    <!-- 注意:分页助手的插件  配置在通用馆mapper之前 -->
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <!-- 指定方言 -->
        <property name="dialect" value="mysql"/>
    </plugin>
</plugins>

开始分页查询

//方式一:getmapper
UserDao mapper = sqlSession.getMapper(UserDao.class);//获取对象
//设置分页查询参数
PageHelper.startPage(1,2);//pagenumber、pagesize
List<User> users = mapper.findAll();
System.out.println(users.get(0));

如果需要获取总页数总条数等分页相关数据,只需要创建一个PageInfo对象,把刚刚查询出的返回值做为构造方法参数传入。然后使用pageInfo对象获取即可。

PageInfo<User> pageInfo = new PageInfo<User>(users);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());

一对多多表查询分页问题

我们在进行一对多的多表查询时,如果使用了PageHelper进行分页,会出现关联数据不全的情况。我们可以使用分步查询的方式解决该问题。

Mybatis缓存

Mybatis的缓存其实就是把之前查到的数据存入内存(map),下次如果还是查相同的东西,就可以直接从缓存中取,从而提高效率。

Mybatis有一级缓存和二级缓存之分,一级缓存(默认开启)是sqlsession级别的缓存。二级缓存相当于mapper级别的缓存。

一级缓存

几种不会使用一级缓存的情况

  • 调用相同的方法但是传入的参数不同
  • 调用相同的方法参数也相同,但是使用的是另外一个sqlSession
  • 如果查询完后,对同一个表进行了增、删、改的操作,都会清空这sqlSession上的缓存
  • 如果手动调用sqlSession的clearCache方法清除缓存了,后面也使用不了缓存

二级缓存

如果使用二级缓存,实体类需要实现Serializable接口。二级缓存在实际开发中基本不会使用,因为某个session修改数据后,另一个再去查缓存会导致数据混乱。

注意:只在sqlsession调用了close或者commit后的数据才会进入二级缓存,同时即使换了个sqlSession二级缓存也会存在。

第一步:全局开启,在Mybatis核心配置文件中配置

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

第二步:局部开启,在要开启二级缓存的mapper映射文件中设置 cache标签

<?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="com.sangeng.dao.RoleDao">
    <cache></cache>
</mapper>

获取插入数据的主键

XML方式

返回自增主键:

<insert id="insert1" useGeneratedKeys="true" keyProperty="id">
    insert into user(username,password) values(#{userName},#{password})
</insert>

返回非自增主键:

<insert id="insert2">
    insert into user(username,password) values(#{userName},#{password})
    <selectKey keyColumn="id" resultType="int" keyProperty="id" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
</insert>

注解方式

返回自增主键:

@Insert("insert into user(username,password) values(#{userName},#{password})")
@Options(keyColumn="id",keyProperty="id",useGeneratedKeys=true)
int insert(SysUser user);

返回非自增:

@Insert("insert into user(username,password) values(#{userName},#{password})")
@SelectKey(statement="SELECT LAST_INSERT_ID()",
           keyProperty="id",
           resultType=Integer.class,
           before = false)
int insert(SysUser user);
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐