Mybatis

视频

Mybatis

  • 框架的基本概念
    • 三层架构
      • 结构
      • 三层架构的优点
    • 框架
      • 常用的框架:SSM
      • 什么是框架?
  • Mybatis
    • 1. 创建Mybatis工程
      • 1.1 创建Maven项目
      • 1.2 在pom.xml中添加mybatis和mysql的依赖
      • 1.3 IDEA中配置MySQL可视化工具
      • 1.4 resources中添加数据库连接的配置文件
      • 1.5 全局配置文件SqlMapConfig.xml
      • 1.6 Mybatis动态代理的7个规范(重要)
      • 1.7 创建Mapper目录
        • 1.7.1 创建UsersMapper接口文件
        • 1.7.2 创建UsersMapper.xml文件
      • 1.8 测试
      • 1.9 #{}与${}
        • 1.9.1 #{}:占位符
        • 1.9.2 ${}字符串拼接或字符串替换(多参数传入,参数别名)
        • 1.9.3. 对#{}与${}的测试
    • 2. 动态sql
      • 2.1 新增数据时返回主键id
      • 2.2 UUID:全球唯一字符串
      • 2.3 `` 自定义代码片段
      • 2.4 `` :实现多条件查询
      • 2.5 ``:条件判断
      • 2.6 ``:更新数据
      • 2.7 ``循环遍历:条件查询、批量删除、批量更新
        • 2.7.1 与select实现批量查询
        • 2.7.2 与insert实现批量新增
        • 2.7.3 与update实现批量更新
    • 3. 多个参数
      • 3.1 按照参数的顺序取值
      • 3.2 给参数取别名
      • 3.3 Map作为参数的应用
    • 4. 多表查询
      • 4.1 返回值为map
        • 4.1.1 返回一行数据
        • 4.1.2 返回多行数据
      • 4.2 列名与实体类的成员变量名不一致
        • 4.2.1 通过别名来解决
        • 4.2.2 使用map映射解决
      • 4.3 一对多关联查询
      • 4.4 多对一关联查询
      • 4.5 一对一
      • 4.6 多对多
    • 5. 事务管理,手动、自动提交
    • 6. 缓存
  • 面向接口编程的四条要点
  • 动态代理

框架的基本概念

三层架构

结构

  1. 界面层:用来接收客户端的输入,调用业务逻辑层进行功能处理,返回结果给客户端,servlet就是界面层的功能
  2. 业务逻辑层:用来进行整个项目的业务逻辑处理,向上为界面层提供结果,向下问数据访问层要数据。
  3. 数据访问层:专门用来进行数据库的增删改查操作,向上为业务逻辑层提供数据。(Mybatis)

各层之间的调用顺序是固定的,不允许跨层访问:
界面层<--------------->业务逻辑层<----------------->数据访问层

三层架构的优点

  1. 结构清晰、耦合度低、各层分工明确
  2. 可维护性高、可靠性高
  3. 有利于标准化
  4. 开发人员可以只关注整个结构中的某一层的功能实现
  5. 有利于各层逻辑的复用

框架

常用的框架:SSM

  1. Spring:整个其他框架的框架,核心IOC(控制反转)和AOP(面向切面编程),由20多个模块构成,很多领域提供了很好的解决方案
  2. SpringMVC:Sping家族的一员,专门用来优化控制器(Servlet),提供了极简单的数据提交,数据携带,页面跳转等功能。
  3. MyBatis:是持久化层的一个框架,用来进行数据库访问的优化,专注于sql语句,极大的简化了JDBC的访问

什么是框架?

是一个半成品的软件,将所有的公共的,重复的功能解决掉,帮助程序快速高效的进行开发,它是可复用的,可扩展的。

Mybatis

主要做JDBC访问的优化
Mybatis_第1张图片

1. 创建Mybatis工程

1.1 创建Maven项目

  • 添加两个resources资源文件夹,目录结构:
    Mybatis_第2张图片

1.2 在pom.xml中添加mybatis和mysql的依赖

    <dependency>
      <groupId>org.mybatisgroupId>
      <artifactId>mybatisartifactId>
      <version>3.5.6version>
    dependency>
    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
      <version>5.1.32version>
    dependency>
  • 添加资源文件夹,拷贝文件到target
  <build>
    <resources>
      <resource>
        <directory>src/main/javadirectory>
        <includes>    
          <include>**/*.propertiesinclude>
          <include>**/*.xmlinclude>
        includes>
        <filtering>falsefiltering>
      resource>
      <resource>
        <directory>src/main/resourcesdirectory>
        <includes>    
          <include>**/*.propertiesinclude>
          <include>**/*.xmlinclude>
        includes>
        <filtering>falsefiltering>
      resource>
    resources>
  build>

1.3 IDEA中配置MySQL可视化工具

Mybatis_第3张图片

1.4 resources中添加数据库连接的配置文件

文件名:jdbc.properties

	jdbc.driverClassName=com.mysql.jdbc.Driver
	jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
	jdbc.username=root
	jdbc.password=root

1.5 全局配置文件SqlMapConfig.xml

Mybatis_第4张图片

全局配置


DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="jdbc.properties">properties>

    <settings>

        <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>

    <typeAliases>
        <package name="org.example.pojo"/>
    typeAliases>

    <environments default="development">
        <environment id="development">

            <transactionManager type="JDBC">transactionManager>

            <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>
    
    <mappers>



        <package name="org.example.mapper">package>
    mappers>
configuration>

1.6 Mybatis动态代理的7个规范(重要)

以User实体类为例

  1. UsersMapper.xml文件与UsersMapper.java接口文件必须在同一目录下。
  2. UsersMapper.xml文件与UsersMapper.java接口文件 文件名必须一致,后缀不管
  3. UsersMapper.xml文件中标签的id值与UsersMapper.java接口文件的方法名必须一致
  4. UsersMapper.xml文件中标签的parameterType属性值与UsersMapper.java接口中方法的参数类型必须一致
  5. UsersMapper.xml文件中标签的resultType属性值与UsersMapper.java接口的**返回值类型(返回值为List则填写泛型的类型)**必须一致
  6. UsersMapper.xml文件namespace属性值必须是UserMaper.java接口的完全限定名称
  7. 在SqlMapConfing.xml文件中注册UserMaper.java接口时,class=接口的完全限定名称

1.7 创建Mapper目录

根据规范第1条:创建一个文件夹
根据规范第2条:新建同名的接口和xml文件
1.7.1 创建UsersMapper接口文件
public interface UsersMapper {
    // 获取全部数据
    List<Users> getAll();
    // 通过id获取数据
    Users getUser(Integer id);
    //更新数据
    int updataUser(Users users);
    // 按名字模糊查询
    List<Users> getUserByName(String userName);
    // 插入用户
    int insertUser(Users users);
    // 根据id删除用户
    int deleteUser(Integer id);
}
1.7.2 创建UsersMapper.xml文件

属性parameterType:传入的参数。如果传入的参数为实体类,值为实体类的驼峰命名法。如果为其他则可省略此属性。
属性resultType:返回值。返回的值为实体类的类型或实体类的List集合,值为实体类的驼峰命名法。如果为其他则可省略此属性。

注意:

  1. 查询、更新、删除、插入操作的标签不同
  2. 更新、删除、插入操作没有返回值属性

DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="org.example.mapper.UsersMapper">



    
    <select id="getUser" resultType="Users" parameterType="Integer"> 
        select id,username,birthday,sex,address from users where id=#{id}
    select>
    
    <select id="getAll" resultType="Users">
        select id,username,birthday,sex,address from users
    select>

  
    <update id="updataUser" parameterType="users">
        update users set username=#{userName},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id}
    update>

    <select id="getUserByName" parameterType="String" resultType="Users">
        select id,username,birthday,sex,address from users where username like '%${name}%'
    select>

    <insert id="insertUser" parameterType="users">
        insert into users (username,birthday,sex,address) values (#{userName},#{birthday},#{sex},#{address})
    insert>

    <delete id="deleteUser" parameterType="Integer">
        delete from users where id=#{id}
    delete>
mapper>

1.8 测试

通过动态代理执行sql语句:

  1. 获取代理对象:mapper = sqlSession.getMapper(UsersMapper.class);
  2. 目标对象UsersMapper.xml实现了UsersMapper接口中的方法。
  3. 使用代理对象调用目标对象实现的方法getAll():List all = mapper.getAll();

注意: 更新、删除、插入操作需要手动提交事务

public class MyTest {
    SqlSession sqlSession;
    UsersMapper mapper;
//    日期格式化工具
    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
//   @Before作用:在所有方法执行前调用执行
    @Before
    public void openSession() throws IOException {
//   1.  读取核心配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//   2. 创建工厂对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//   3. 取出sqlsession对象
        sqlSession = factory.openSession();
//   4. 获取UsersMapper.xml的代理对象
        mapper = sqlSession.getMapper(UsersMapper.class);
    }
//    @After作用:有所有方法执行完毕后调用执行
    @After
    public void closeSession(){
        sqlSession.close();
    }
//    获取所有数据
    @Test
    public void testGetAll(){
//   5. 代理对象执行目标对象实现的接口中的方法
        List<Users> all = mapper.getAll();
        System.out.println(all);
    }
//    获取id的数据
    @Test
    public void testGetUser(){
        Users user= mapper.getUser(3);
        System.out.println(user);
    }
//    更新数据
    @Test
    public void updataUser() throws ParseException {
        Users users = new Users(3,"张大明", sf.parse("2000-1-1"),"1","北京朝阳");
        int n = mapper.updataUser(users);
        sqlSession.commit();//手动提交事务
    }
    // 模糊查询
    @Test
    public void testgetUserByName(){
        List<Users> users = mapper.getUserByName("三");
    }
    // 插入数据
    @Test
    public void testInsertUser() throws ParseException {
        int n = mapper.insertUser(new Users("呵呵", sf.parse("2002-2-2"), "2", "上海陆家嘴"));
        sqlSession.commit();
    }
    //根据id删除用户
    @Test
    public void testDeleteUser(){
        int n = mapper.deleteUser(31);
        sqlSession.commit();
    }
}

1.9 #{}与${}

1.9.1 #{}:占位符
  1. 如果parameterType传入的参数是普通类型则#{}里面随便写
  2. 如果parameterType传入的参数是引用类型,则#{}里面就要写实体类中成员变量的名称,而且区分大小写。
1.9.2 ${}字符串拼接或字符串替换(多参数传入,参数别名)
  1. 字符串拼接,一般用于模糊查询,有sql注入风险。模糊查询一律使用sql语言中concat方法和#{}拼接字符串,示例:'%${name}%'改写为:concat ('%',#{name},'%')
  2. ${}最重要的作用-----字符串替换:
    下面代码实现的功能:随意指定列进行模糊查询。
// 接口文件
    // 按名字模糊查询,高级版
    List<Users> getUserByNameBest(
    		// 使用注解@Param给参数设置别名
            @Param("columnName")
            String columnName,
            @Param("columnValue")
            String columnValue);

测试代码中传入两个参数,第一个参数使用${}会替换为email字符串,第二个参数使用占位符#{},防止sql注入。


    
    <select id="getUserByNameBest" parameterType="String" resultType="Users">
        select id,name,email,age from users where ${columnName} like concat ('%',#{columnValue},'%')
    select>

测试代码:

    // 模糊查询 高级版 指定列模糊查询
    @Test
    public void testgetUserByNameBest(){
        List<Users> users = mapper.getUserByNameBest("email","126");
    }
1.9.3. 对#{}与${}的测试

使用1.9.2中的代码

# 第一种情况
select * from users where ${columnName} like concat ('%',#{columnValue},'%')
#执行后
select * from users where email like concat ('%',?,'%')
# 可以看到#{}是占位符,${columnName}替换为字符串:email

# 第二种情况
select * from users where #{columnName} like concat ('%',${columnValue},'%')
# 执行后
select * from users where ? like concat ('%',126,'%')
# 从上面两个例子可以很明确的看出#{}与${}的作用

2. 动态sql

2.1 新增数据时返回主键id

功能:在执行完相关代码后自动返回主键的id值到入参的实体类中。

        <selectKey keyProperty="id" resultType="int" order="AFTER">
            select last_insert_id()
        selectKey>

selectKey 标签的参数详解:

  1. keyProperty:主键在数据库表中的列名
  2. resultType:主键的类型,可能是UUID
  3. order标签体内的代码的执行时机
  4. 标签体内:要执行的sql代码
  5. last_insert_id():sql语言中的函数也是获取最后插入的记录的id

示例:
接口的mapper.xml文件

    
    <insert id="insertUserReturnId" parameterType="users">
        <selectKey keyProperty="id" resultType="int" order="AFTER">
            select last_insert_id()
        selectKey>
        insert into users (name,email,age) values (#{name},#{email},#{age})
    insert>

测试代码

    // 插入数据,并返回主键id
    @Test
    public void testInsertUserReturnId() throws ParseException {
        Users user = new Users("张小明", "[email protected]", 3);
        int n = mapper.insertUserReturnId(user);
        sqlSession.commit();
        System.out.println(user);
    }

输出:Users{id=11, name='张小明', email='[email protected]', age=3}
结论: 新增数据,主键值自动返回给传入的实体类参数。

2.2 UUID:全球唯一字符串

java中生成UUID

    // UUID  全球唯一字符串
    @Test
    public void testUUID(){
        UUID uuid = UUID.randomUUID();
        System.out.println("uuid = " + uuid);
    }

sql中生成UUID

	select uuid()

2.3 自定义代码片段

自定义代码片段:,可以将复杂、重复的代码自定义为片断。属性id的值自定义
使用自定义的代码片段:用来引用定义的代码片断。属性refid的值为片断中属性id的值

示例:


    <sql id="allColumns">
        id,name,email,age
    sql>

    <select id="getUser" resultType="Users" parameterType="Integer">
        select <include refid="allColumns">include> from users where id=#{id}
    select>

2.4 :实现多条件查询

功能:标签代替sql语句中的where,在标签内通过使用标签等进行条件判断,达到多条件查询的目的

注意: 入参为实体类

示例:同下面 2.5 :条件判断

2.5 :条件判断

功能:条件为真,则使用标签内的代码

注意:

  1. 标签属性test进行条件判断,注意判断是否是空字符串时使用单引号
  2. 标签内的语句记得加and

示例:

<!--    <where> 实现全条件模糊查询-->
    <select id="getAllWhere" parameterType="users" resultType="users">
        select <include refid="allColumns"></include> 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>

2.6 :更新数据

原来的更新出现的问题:

  • 根据id,修改姓名,实体类中只有id和name有值,其他为null
    数据更新时如果实体类的成员变量只有部分有值其他为null,更新数据库后出现下面的情况
    在这里插入图片描述

解决上述问题,则需要判断传入的对象中每个成员变量是否有值来确定要更新的列

标签的作用: 代替更新语句中的set关键字,可在标签内使用判断成员变量是否有值,来决定是否更新某列。

注意:

  1. 更新操作最少要有一条数据被更新,否则会报错
  2. 标签包裹的代码,记得加,号。

示例:

<!-- 动态sql,更新数据 <set>标签-->
    <update id="updataUserSet" 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>

2.7 循环遍历:条件查询、批量删除、批量更新

  • 标签内的属性:
  1. collection:固定值。取决于接口中方法定义的参数。常用值:list、array、map
  2. item:相当于java中foreach遍历时的当前元素,值自定义
  3. separator:间隔符号
  4. open:标签开始前的括号
  5. close:标签结束后的括号
2.7.1 与select实现批量查询

查询表中名字是王五和张三的数据,原生sql语句如下

select * from users
where username in ('王五','张三')

使用foreach标签


    <select id="getByIds" resultType="Users">
        select <include refid="allColumns">include> from users
        where username in
        
        <foreach collection="array" item="item" separator="," open="(" close=")">
            #{item}
        foreach>
    select>

通过对比可以看出

('王五','张三') 
// 等同于下面的代码
<foreach collection="array" item="item" separator="," open="(" close=")">
    #{item}
</foreach>
2.7.2 与insert实现批量新增

foreach遍历的是实体类users的list集合,所以item的值就是一个users元素,要取实体类中成员变量的值,需要使用user.username。

mapper.xml配置文件:


    <insert id="updataForeach">
        insert into users (username,birthday,sex,address) values
        <foreach collection="list" item="user" separator=",">
            (#{user.userName},#{user.birthday},#{user.sex},#{user.address})
        foreach>
    insert>
2.7.3 与update实现批量更新

注意:
要使用批量更新需要在jdbc的配置文件jdbc.properties的url后面新增代码 &allowMultiQueries=true

  1. 修改jdbc的配置文件
    jdbc.properties配置文件
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
jdbc.username=root
jdbc.password=root
  1. updata与foreach配合是最特殊的一组,foreach中包的不是条件或参数类的东西,而是整个update语句,注意foreach中separator属性的值为
    <update id="updataForeach">
        <foreach collection="list" separator=";" item="item">
            update users
            <set>
                <if test="item.userName != null and item.userName != ''">
                    username = #{item.userName},
                if>
                <if test="item.birthday != null">
                    birthday = #{item.birthday},
                if>
                <if test="item.sex != null and item.sex != ''">
                    sex = #{item.sex},
                if>
                <if test="item.address != null and item.address != ''">
                    address = #{item.address},
                if>
            set>
                where id = #{item.id}
        foreach>
    update>

3. 多个参数

3.1 按照参数的顺序取值

在下面示例中,参数(Date begin,Date end),对应到mapper文件中使用时,按照参数的顺序,依次取值,#{arg0}取得是第一个参数begin的值, #{arg1}取得是第二个参数的值end。无论有多少参数只需要改变#{arg顺序}中的顺序就可以

缺点: 可读性差

mapper.xml文件中代码



    <select id="getBetweenDate" resultType="users">
        select <include refid="allColumns">include>
        from users
        where birthday between #{arg0} and #{arg1}
    select>

3.2 给参数取别名

见示例:1.9.2

3.3 Map作为参数的应用

使用方法

  1. 将要传入的参数,存入map的键值对
  2. 在xml文件中,直接使用map键值对的key就可获取到value的值

示例中传入的map键值对是:{begin=sf.parse("2000-01-01"),end=sf.parse("2002-12-31")}
在使用中,直接通过map的key#{begin} 、 #{end}就可以获取到对应的value值

测试方法

    @Test
    public void testMapGetBEtween() throws ParseException {
        Map<String,Date> map = new HashMap<>();
        map.put("begin", sf.parse("2000-01-01"));
        map.put("end", sf.parse("2002-12-31"));
        mapper.getBetweenDateByMap(map);
    }


    <select id="getBetweenDateByMap"  resultType="users">
        select <include refid="allColumns">include>
        from users
        where birthday between #{begin} and #{end}
    select>

4. 多表查询

4.1 返回值为map

4.1.1 返回一行数据
  • 作用:多表查询时,返回值没有对应的实体类,使用map作为返回类型,map中key就是列名,value就是查询到的值。

注意:xml中返回值类型为map



    <select id="getSimpleMap" resultType="map">
        select name,email
        from users
        where id = #{id}
    select>

输出map的值:{name=王五, [email protected]}

  • 当列有别名时,返回的map中key的值是列的别名


    <select id="getSimpleMap" resultType="map">
        select name as n ,email as e
        from users
        where id = #{id}
    select>

输出map的值:{[email protected], n=王五}

4.1.2 返回多行数据
  • 返回值类型为map,与返回实体类一样,虽然最终得到的是list集合,但是resultType属性的值还是map
<!--    返回多行数据,返回map的list集合-->
<!--        List<Map> getRowsMap();-->
    <select id="getRowsMap" resultType="map">
        select name ,email from users
    </select>

4.2 列名与实体类的成员变量名不一致

问题: 列名与实体类的成员变量名不一致,并且resultType属性的值为实体类名,执行时,代码不报错,但是返回的结果为空。

4.2.1 通过别名来解决
  • 给所有列设置别名,且别名与实体类成员变量名一致

数据库中列名
在这里插入图片描述
实体类中成员变量名
Mybatis_第5张图片
mapper.xml中给列设置别名
在这里插入图片描述

4.2.2 使用map映射解决