• bjpowernode_MyBatis


    动力节点_Mybatis

    适合配套学习的教程:

    ​ Mybatis讲义.doc,连接:https://shimo.im/docx/m5kv9dp2XgcDGaqX/

    ​ MyBatis-3-User-Guide-Simplified-Chinese.pdf,连接:https://shimo.im/files/8Nk6MeN8gJf99nqL/

    1. 什么是SSM.

    • Spring:它是整合其它框架的框架,它的核心是IOC和AOP,它由20多个模块构成。在很多领域都提供了很好的解决方案,是一个大佬级别的存在;
    • SpringMVC:它是Spring家族的一员,专门用来优化控制器(Servlet)的,提供了极简单数据提交,数据携带,页面跳转等功能;
    • MyBatis:是持久化层的一个框架,用来进行数据库访问的优化,专注于sql语句。极大的简化了JDBC的访问;

    什么是框架:

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

    1.1 什么是MyBatis框架

    MyBatis 本是 apache 的一个开源项目iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis 。2013 年 11 月迁移到 Github,最新版本是 MyBatis 3.5.7 ,其发布时间是 2021 年 4月 7日。MyBatis完成数据访问层的优化,它专注于sql语句,简化了过去JDBC繁琐的访问机制。

    添加框架的步骤:

    • 添加依赖
    • 添加配置文件

    1.2 入门案例

    1. 新建库建表

      #创建数据库ssm
      CREATE DATABASE ssm DEFAULT CHARSET utf8;
      
      #使用(打开)ssm数据库
      use ssm;
      
      #创建表student
      CREATE TABLE `student` (
      `id` int(11)  AUTO_INCREMENT primary key ,
      `name` varchar(255) DEFAULT NULL,
      `email` varchar(255) DEFAULT NULL,
      `age` int(11) DEFAULT NULL
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
      
      insert into student(name,email,age) values('张三','zhangsan@126.com',22);
      insert into student(name,email,age) values('李四','lisi@126.com',21);
      insert into student(name,email,age) values('王五','wangwu@163.com',22);
      insert into student(name,email,age) values('赵六','zhaoliun@qq.com',24);
      select * from student;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    2. 新建maven项目,选quickstart模板

      image-20220809221335835

    3. 修改目录,添加缺失的目录,修改目录属性。这里主要是resources文件;

    4. 修改pom.xml文件,添加MyBatis的依赖,添加mysql的依赖;

      
      <dependency>
          <groupId>org.mybatisgroupId>
          <artifactId>mybatisartifactId>
          <version>3.5.7version>
      dependency>
      
      <dependency>
          <groupId>mysqlgroupId>
          <artifactId>mysql-connector-javaartifactId>
          <version>5.1.3version>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    5. 修改pom.xml文件,添加资源文件指定

      <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
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
    6. 在idea中添加数据库的可视化,主要是方便数据管理

      image-20220809221758164

    7. 添加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
      • 2
      • 3
      • 4
    8. 添加SqlMapConfig.xml文件,MyBatis的核心配置文件

      Mybatis核心配置文件头:

      
      DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
          
      configuration>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      加载数据库连接配置信息:
      resource:会自动到资源目录resources下去扫描指定的文件;
      url:这里指定的是绝对地址;

      <properties resource="jdbc.properties"/>
      
      • 1

      配置环境:

      
      <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>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

      注册mapper.xml文件

      
      
      <mappers>
          <mapper resource="StudentMapper.xml"/>
      mappers>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    9. 创建实体类Student,用来封装数据

      public class Student {
          private Integer id;
          private String name;
          private String email;
          private Integer age;
          // getter setter 有参无参构造函数 toString...
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    10. 添加完成学生表的增删改查的功能的StudentMapper.xml文件

      mapper文件头:

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

      mapper标签:

      
      <mapper namespace="student">
           
      mapper>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      查询所有的学生信息:

      
      <select id="getAll" resultType="com.xuan.pojo.Student">
          select id, name, email, age from student;
      select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      按主键id查询学生信息

      
      <select id="getById" parameterType="int" resultType="com.xuan.pojo.Student">
          select id, name, email, age from student where id = #{id}
      select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      按学生姓名进行查询

      
      <select id="getByName" parameterType="string" resultType="com.xuan.pojo.Student">
          select id, name, email, age from student where name like #{name}
      select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      添加学生信息

      Mybatis中,增删改在xml文件中是没有执行的返回结果(即,可以不写其返回值),可以通过SqlSession对象的相关方法获取执行结果。

      
      <insert id="insert" parameterType="com.xuan.pojo.Student">
          insert into student(name, email, age) values (#{name}, #{email}, #{age})
      insert>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      删除学生

      
      <delete id="delete" parameterType="int">
          delete from student where id = #{id}
      delete>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      删除学生

      
      <update id="update" parameterType="com.xuan.pojo.Student">
          update student set name = #{name}, email = #{email}, age = #{age} where id = #{id}
      update>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    11. 创建测试类,进行功能测试

      public class MyTest {
      
          // 优化测试
          SqlSession sqlSession;
      
          /*
          @Before注解:在所有被@Test注解标注的方法执行之前执行被@Before注解标注的方法里面的代码,注解@After则相反。
      
          该方法实现:读取Mybatis核心配置文件、创建SqlSessionFactory、创建SqlSession对象
           */
          @Before
          public void getSqlSession() throws IOException {
              // 使用文件流读取核心配置文件:SqlMapConfig.xml
              InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
              // 创建SqlSessionFactory工厂
              SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
              // 取出sqlSession对象
              sqlSession = factory.openSession();
          }
      
          /**
           * 该方法用来关闭SqlSession对象。
           * 每次执行被@Test注解标注的方法在执行完毕后都会执行该方法。
           */
          @After
          public void closeSqlSession(){
              // 关闭sqlSession,实际上是将该链接放回到连接池
              sqlSession.close();
          }
      
          @Test
          public void testAll() throws IOException {
              // 完成查询操作
              List<Student> studentList = sqlSession.selectList("student.getAll");
              studentList.forEach(System.out::println);
          }
      
          @Test
          public void testSpecificStudent() throws IOException {
              Student student = sqlSession.selectOne("student.getById", 1);
              System.out.println(student);
          }
      
          @Test
          public void testSpecificName() throws IOException {
              List<Student> studentList = sqlSession.selectList("student.getByName", "%三%");
              studentList.forEach(System.out::println);
          }
          @Test
          public void testInsertAndDelAndUpdate() throws IOException {
              // 添加
              //sqlSession.insert("student.insert", new Student(1111, "hh", "hh@hhh.com", 20));
      
              // 修改
              // sqlSession.update("student.update", new Student(5, "babql", "babq@hhh.com", 222));
      
              // 删除
              sqlSession.delete("student.delete", 5);
              // 切记这里要手动提交事务
              sqlSession.commit();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62

    1.3 MyBatis对象分析

    Resources类
    就是解析SqlMapConfig.xml文件,创建出相应的对象,代码如下:

    InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
    
    • 1

    SqlSessionFactory接口

    使用ctrl+h快捷键查看本接口的子接口及实现类:image-20220809223452577

    这里可以看到,DefaultSqlSessionFactory是SqlSessionFactory接口的实现类,通过new SqlSessionFactoryBuilder().build(in)这种方式创建SqlSession对象时,底层使用的就是DefaultSqlSessionFactory实现类。

    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
    
    • 1

    image-20220809223232748

    public SqlSessionFactory build(Configuration config) {
      return new DefaultSqlSessionFactory(config);
    }
    
    • 1
    • 2
    • 3

    SqlSession接口image-20220809222655276

    DefaultSqlSession是SqlSession主要的实现实现类。SqlSession中的很多方法都是在DefaultSqlSession中进行实现的。

    1.4 补充

    为实体类注册别名:

    
    <typeAliases>
        
        
    
        
        <package name="com.xuan.pojo"/>
    typeAliases>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    设置日志输出:

    
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    settings>
    
    • 1
    • 2
    • 3
    • 4

    2. 动态代理存在意义

    在三层架构中,业务逻辑层要通过接口访问数据层的功能,但是这里有个问题,就是业务逻辑层不能直接方法DAO层xml文件中的方法(功能),所以这里使用到了动态代理来解决这也问题。

    image-20220810110652840

    2.1 动态代理的实现规范

    1. UsersMapper.xml文件与UsersMapper.java的接口必须同一个目录下;
    2. UsersMapper.xml文件与UsersMapper.java的接口的文件名必须一致,后缀不一样;
    3. UserMapper.xml文件中标签的id值与UserMapper.java的接口中方法的名称完全一致;
    4. UserMapper.xml文件中标签的parameterType属性值与UserMapper.java的接口中方法的参数类型完全一致;
    5. UserMapper.xml文件中标签的resultType值与UserMapper.java的接口中方法的返回值类型完全一致;
    6. UserMapper.xml文件中namespace属性必须是接口的完全限定名称com.bjpowernode.mapper.UsersMapper;
    7. 在SqlMapConfig.xml文件中注册mapper文件时,使用class=接口的完全限定名称com.bjpowernode.mapper.UsersMapper;

    2.2 动态代理访问的步骤

    1. 建表Users;
    2. 新建maven工程,刷新可视化;
    3. 修改目录;
    4. 修改pom.xml文件,添加依赖;
    5. 添加jdbc.propertis文件到resources目录下;
    6. 添加SqlMapConfig.xml文件;
    7. 添加实体类;
    8. 添加mapper文件夹,新建UsersMapper接口;
    9. 在mapper文件夹下,新建UsersMapper.xml文件,完成增删改查功能;
    10. 添加测试类,测试功能;

    12.优化mapper.xml文件注册;

    Users数据库表:

    use ssm;
    -- ----------------------------
    -- Table structure for `users`
    -- ----------------------------
    DROP TABLE IF EXISTS `users`;
    CREATE TABLE `users` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(32) COMMENT '用户名称',
      `birthday` date DEFAULT NULL COMMENT '生日',
      `sex` char(2) DEFAULT NULL COMMENT '性别',
      `address` varchar(256) DEFAULT NULL COMMENT '地址',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=27 DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of user
    -- ----------------------------
    INSERT INTO `users` VALUES (1, '王五', '2000-09-10', '2', '安徽');
    INSERT INTO `users` VALUES (2, '张三', '2001-07-12', '1', '北京市');
    INSERT INTO `users` VALUES (3, '张小明', '1999-02-22', '1', '河南');
    INSERT INTO `users` VALUES (4, '陈小亮', '2002-11-19', '1', '辽宁');
    INSERT INTO `users` VALUES (5, '张三丰', '2001-03-10', '1', '上海市');
    INSERT INTO `users` VALUES (6, '陈小明', '2002-01-19', '1', '重庆市');
    INSERT INTO `users` VALUES (7, '王五四', '2001-05-13', '2', '天津市');
    select * from users;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    注册mapper.xml文件

     
    <mappers>
        
        <mapper url="/">mapper>
        
        <mapper resource="StudentMapper.xml">mapper>
        
        <mapper class="com.bjpowernode.mapper.UsersMapper">mapper>
        
        <package name="com.bjpowernode.mapper">package>
    mappers> 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试类

    public class MyTest {
        SqlSession sqlSession;
        // Mybatis动态代理出来的对象
        UserMapper userMapper;
        // 格式化时间对象
        SimpleDateFormat spf;
        @Before
        public void getSqlSession() throws IOException {
            // 加载核心配置文件
            InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
            // 获取工厂
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
            //获取SqlSession对象
            sqlSession = factory.openSession();
    
            // 取出动态代理的对象,完成接口中方法的调用,实则是调用xml文件中相对应标签的功能
            userMapper = sqlSession.getMapper(UserMapper.class);
    
            // 格式化时间对象,年月日
            spf = new SimpleDateFormat("yyyy-MM-dd");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    2.3 #{}与${}

    #{}占位符:

    传参大部分使用#{}传参,它的底层使用的是PreparedStatement对象,是安全的数据库访问 ,可以有效的防止sql注入问题。#{}里如何写,这里主要是看parameterType参数的类型:

    • 如果parameterType的类型是Java基本数据类型(或其封装类)与String,则#{}里面可以随便写;

      
      <select id="getUserByUsername" resultType="user" parameterType="string">
          select id, username, birthday, sex, address from users where username like #{username}
      select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 如果parameterType的类型是实体类的类型,则#{}里只能是类中成员变量的名称,而且区分大小写;

      
      <update id="update" parameterType="user">
          update users set username = #{username}, birthday = #{birthday}, sex = #{sex}, address = #{address}
          where id = #{id}
      update>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    ${}: 通常用于字符串拼接或字符串替换,具体如下:

    • 字符串拼接,一般用于模糊查询中.建议少用,因为有sql注入的风险。 也分两种情况,同样的看parameterType的类型:

      • A. 如果parameterType的类型是简单类型,则${}里随便写,但是分版本,如果是3.5.1及以下的版本,只能写value;
      ```xml
      
      ```
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 如果parameterType的类型是实体类的类型,则${}里只能是类中成员变量的名称(现在已经少用);

      • 优化后的模糊查询(以后都要使用这种方式);

        
        <select id="getUserByUsername" resultType="user" parameterType="string">
            select id, username, birthday, sex, address from users where username like concat('%', #{username}, '%')
        select>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
    • 字符串替换(使用的还有点多)

      需求:要求模糊查询地址或用户名;

      select * from users where username like '%小%';
      select * from users where address like '%市%';
      
      • 1
      • 2

      这里如果是按照上面的方式写sql,那么可能就要对应两个不同的方法,所以这里可以使用替换的方式使用一个方法进行实现。

    /**
     * 通过列名与列值获取对应的用户信息。这里@Param参数value可以与被修饰的参数名不一样,但是规范要求一样。
     * @param columnName 对应表的列的名称,为了能够在xml文件中正确的取到该值,这两个参数必须使用@Param注解进行修饰。
     * @param columnValue 对应表的列的值。
     * @return 返回查询的用户的信息。
     */
    List<User> getUserByUsernameOrAddr(
            @Param("columnName")
            String columnName,
            @Param("columnValue")
            String columnValue
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    
    <select id="getUserByUsernameOrAddr" resultType="user">
        select id, username, birthday, sex, address
        from users
        where ${columnName} like concat('%', #{columnValue}, '%')
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.4 返回主键值

    业务描述:当新增一个用户之后,给用户id积分自动加500。

    这里涉及一个问题,用户表中的uid字段是自增,当添加用户时,用户的id是自动生成的,那么这里在新增一个用户后,如何同时获取到自动生成的uid给用户在积分表中添加积分值?这里可以使用mysql中的一个函数last_insert_id(),该函数会自动返回最新插入到表中的数据行对应的id。下面来解决这个问题。

    image-20220810211424134

    在插入语句结束后, 返回自增的主键值到入参的users对象的id属性中。

    /**
     * 新增用户,添加之后返回用户的id。
     * @param user 添加的对象;
     * @return 返回影响的行数;
     */
    int insert(User user);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    <!--
    int insert(User user);
    
    keyProperty:使用user对象的id属性来接收返回的主键值;
    resultType:返回的主键值的类型;
    order:在插入语句执行之前还是之后返回主键的值;
    -->
    <insert id="insert" parameterType="user">
        <selectKey keyProperty="id" resultType="int" order="AFTER">
            select last_insert_id();
        </selectKey>
        insert into users(username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address})
    </insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    @Test
    public void testInsert() throws ParseException {
        User user = new User(11, "fjaidfdasfad", spf.parse("1999-02-03"), "2", "广东深圳");
        userMapper.insert(user);
        sqlSession.commit();
        System.out.println(user);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.4 UUID

    这是一个全球唯一随机字符串,由36个十六进制数以及中划线组成。

    // UUID
    @Test
    public void testUUID(){
        UUID uuid = UUID.randomUUID();
        System.out.println(uuid.toString().replace("-", ""));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. 动态sql

    可以定义代码片断,进行逻辑判断,循环处理(批量处理),使条件判断更为简单;

    3.1 代码片段

    • :用来定义代码片断,可以将所有的列名,或复杂的条件定义为代码片断,供使用时调用,实现sql的重复使用;

      
      <sql id="allColumns">
          id, username, birthday, sex, address
      sql>
      
      • 1
      • 2
      • 3
      • 4
    • :用来引用定义的代码片断;

      <select id="allUsers" resultType="user">
          select <include refid="allColumns"/> from users
      select>
      
      • 1
      • 2
      • 3

    3.2 逻辑判断

    • :进行条件判断;

      test条件判断的取值可以是实体类的成员变量,可以是map的key,可以是@Param注解的名称

    • :进行多条件拼接,在查询,删除,更新中使用;

      /**
       * 进行多条件查询。
       * @param user 传入一个对象,判断对象是否为null,如果不为空,则该属性值即为查询条件之一。
       * @return 查询的对象;
       */
      List<User> moreConditionQuery(User user);
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      
      <select id="moreConditionQuery" resultType="user" parameterType="user">
          select <include refid="allColumns"/>
          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 = #{sex}
              if>
              <if test="address != null and address != ''">
                  and address like concat('%', #{address}, '%')
              if>
          where>
      select>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      // 多条件查询
      @Test
      public void testMoreConditionQuery(){
          // 默认为查询所有的用户
          User user = new User();
          // 查询用户名中包含“小”的用户
          user.setUsername("小");
          // 并且查询所在地址中包含“市”的字
          user.setAddress("市");
          List<User> userList = userMapper.moreConditionQuery(user);
          userList.forEach(System.out::println);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      底层执行的sql语句分析,以上的sql使用了与标签进行了条件的判断。当查询的用户对象所有的属性全为空时,底层执行的sql为:

      image-20220811144822399

      当用户属性有一个不为空时,这里以用户名为小作为查询条件。执行的sql为:

      image-20220811145043222

      这里有个问题值得注意,就是我们在xml文件中,已经写了当用户名不为空时将用户名加入查询的条件:

      <if test="username != null and username != ''">
          and username like concat('%', #{username}, '%')
      if>
      
      • 1
      • 2
      • 3

      可以看到用户名前面的and已经被丢弃了,所以,当以多个参数作为条件时,其中如果有一个参数不为空,则会为该sql自动加上where条件,并且第一个不为空的参数前面的and会被省略。

    3.3 更新操作

    :有选择的进行更新处理,至少需要更新一列。 能够保证如果没有传值进来,则数据库中的数据保持不变;注意点:前面演示的更新操作中其实都存在问题,即当用户传入的user对象只有username属性不为空,这时如果进行更新操作,其他的字段都会被替换为null。

    image-20220811150927768

    image-20220811151049487

    所以在进行更新操作前需要判断用户出入的属性是否为空,如果为空该字段就不进行更新。

    /**
     * 选择性的进行更新操作。
     * @param user 更新的用户;
     * @return 返回影响行数;
     */
    int conditionUpdate(User user);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    
    <update id="conditionUpdate" parameterType="user">
        update users
        <set>
            <if test="username != null and username != ''">
                username = #{username},
            if>
            <if test="birthday != null">
                birthday = #{birthday},
            if>
            <if test="sex != null">
                sex = #{sex},
            if>
            <if test="address != null and address != ''">
                address = #{address},
            if>
        set>
        where id = #{id}
    update>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    // 选择更新
    @Test
    public void testConditionUpdate(){
        User user = new User();
        user.setId(9);
        user.setUsername("张宇");
        userMapper.conditionUpdate(user);
        sqlSession.commit();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    底层执行的sql语句为:

    image-20220811153126438

    这种方式必须至少更新一列,否则回报一下的错误:

    image-20220811173748760

    3.4 遍历操作

    < foreach>:用来进行循环遍历,完成循环条件查询,批量删除(常用),批量增加(偶尔用),批量更新(很少用);

    查询实现:

    
    <select id="queryMoreUserById" resultType="user">
        select <include refid="allColumns"/>
        from users
        where id in
        <foreach collection="array" item="id" separator="," open="(" close=")">
            #{id}
        foreach>
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    
    <delete id="batchDel">
        delete from users
        where id in
        <foreach collection="array" item="id" separator="," open="(" close=")">
            #{id}
        foreach>
    delete>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    
    <insert id="batchInsert">
        insert into users(username, birthday, sex, address) VALUES
        <foreach collection="list" separator="," item="user">
            (#{user.username}, #{user.birthday}, #{user.sex}, #{user.address})
        foreach>
    insert>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.5 指定参数位置

    如果入参是多个,可以通过指定参数位置进行传参。如果是实体包含不住的条件参数类型,那么就只能使用形参这种方式了。例如:查询指定日期范围内的用户信息。

    
    /*
    一个方法有多个参数,且这些参数无法通过一个对象进行封装。这里有两种解决方案(不推荐使用):
        1. 使用@Param()注解;
        2. 使用#{args0},#{args1}...
     */
    // 查询指定日期出生的用户
    List<User> queryByBirthday(Date beginDate, Date endDate);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    
    <select id="queryByBirthday" resultType="user">
        select <include refid="allColumns"/>
        from users
        where birthday between #{arg0} and #{arg1}
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    @Test
    public void testQueryByBirthday() throws ParseException {
        Date beginDate = spf.parse("2002-01-01");
        Date endDate = spf.parse("2002-12-31");
        List<User> users = userMapper.queryByBirthday(beginDate, endDate);
        users.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.6 入参是Map(重点掌握)

    如果入参超过一个以上,使用map封装查询条件,更有语义,查询条件更明确。

    // 使用Map对上面的方法进行优化(推荐使用,也是应该重点掌握的)
    List<User> queryByBirthdayMap(Map<String, Date> map);
    
    • 1
    • 2
    
    <select id="queryByBirthdayMap" resultType="user">
        select <include refid="allColumns"/>
        from users
        where birthday between #{beginDate} and #{endDate}
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    @Test
    public void testQueryByBirthdayMap() throws ParseException {
        Map<String, Date> map = new HashMap<>();
        Date beginDate = spf.parse("2002-01-01");
        Date endDate = spf.parse("2002-12-31");
        map.put("beginDate", beginDate);
        map.put("endDate", endDate);
        List<User> users = userMapper.queryByBirthdayMap(map);
        users.forEach(System.out::println);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.7 返回值是Map

    如果返回的数据实体类无法包含,可以使用Map返回多张表中的若干数据.返回后这些数据之间没有任何的内在联系。就是Object类型,返回的Map的key就是查询查询sql语句的列名或别名。

    
    <select id="queryReturnMap" resultType="map" parameterType="int">
        select username, address
        from users
        where id = #{id}
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    
    <select id="queryMulMap" resultType="map">
        select username, address
        from users
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.8 解决列名不一致的问题

    sql代码:

    USE ssm;
    DROP TABLE IF EXISTS `book`;
    CREATE TABLE `book` (
      `bookid` INT(11) NOT NULL AUTO_INCREMENT,
      `bookname` VARCHAR(32) NOT NULL COMMENT '图书名称',
       PRIMARY KEY (`bookid`)
    ) ENGINE=INNODB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8;
    
    
    INSERT INTO `book` VALUES (1, 'java基础');
    INSERT INTO `book` VALUES (2, 'sql基础');
    
    SELECT * FROM book;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    当实体类的属性与表中的字段名不一致的时候,这是无法直接对数据进行封装,通常有以下的解决方案:

    • 写sql的时候通过给sql取别名的方式让实体类的属性名与这个别名一样;
    • 使用resultMap手工绑定的方式解决;

    a>方案一

    image-20220812171200508

    public class Book {
        private Integer id;
        private String name;
        
        // ... gettter and setter and constructor and toString()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    
    <select id="allBook" resultType="book">
        select bookid id, bookname name from book
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    b>方案二(推荐使用)

    
    <resultMap id="bookMap" type="book">
        
        <id property="id" column="bookid"/>
        
        <result property="name" column="bookname"/>
    resultMap>
    <select id="allBook" resultMap="bookMap">
        select bookid, bookname from book
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4. 表之间的关联关系

    关联关系是有方向的。

    • 一对多关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在老师方,就是一对多关联。
    • 多对一关联:一个老师可以教多个学生,多个学生只有一个老师来教,站在学生方,就是多对一关联。
    • 一对一关联:一个老师辅导一个学生,一个学生只请教一个老师.学生和老师是一对一。
    • 多对多关联:园区划线的车位和园区的每一辆车,任意一个车位可以停任意一辆车,任意一车辆车可以停在任意一个车位上。

    4.1 一对多关联关系

    sql代码:

    USE ssm;
    
    CREATE TABLE customer(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	NAME VARCHAR(32),
    	age INT2
    );
    INSERT INTO customer VALUES(1,'张三',22);
    INSERT INTO customer VALUES(2,'李四',23);
    INSERT INTO customer VALUES(3,'王五',24);
    
    CREATE TABLE orders(
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	orderNumber VARCHAR(16),
    	orderPrice DOUBLE,
    	customer_id INT 
    );
    INSERT INTO orders VALUES(11,20,22.22,1);
    INSERT INTO orders VALUES(12,60,16.66,1);
    INSERT INTO orders VALUES(13,90,19.99,2);
    
    SELECT * FROM customer;
    SELECT * FROM orders;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    客户和订单就是典型的一对多关联关系,一个客户名下可以有多个订单。客户表是一方,订单表是多方,通常一个客户表中持有订单的集合,使用一对多的关联关系,可以满足查询客户的同时查询该客户名下的所有订单。

    image-20220812211621539

    
    <select id="allCustomer" resultMap="customerMap" parameterType="int">
        select c.id cid, name, age, o.id, ordernumber, orderprice, customer_id
        from customer c left join orders o on c.id = o.customer_id
        where c.id = #{id};
    select>
    
    <resultMap id="customerMap" type="customer">
        
        <id property="id" column="cid"/>
        
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        
        <collection property="ordersList" ofType="orders">
            
            <id property="id" column="oid"/>
            
            <result property="orderNumber" column="orderNumber"/>
            <result property="orderPrice" column="orderPrice"/>
        collection>
    resultMap>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    4.2 多对一关联关系.

    订单和客户就是多对一的关联,站在订单的方向查询订单的同时将客户信息查出。订单是多方,持有一方的对象,客户是一方。

    image-20220812211551551

    package com.xuan.mapper;
    
    import com.xuan.pojo.Orders;
    
    public interface OrdersMapper {
        // 查询指定的订单包括下单的用户,注:为了简单起见,这里的用户只有一个,即:订单与用户为多对一的关系。
        Orders getOrderById(Integer id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    <mapper namespace="com.xuan.mapper.OrdersMapper">
        
        <select id="getOrderById" parameterType="int" resultMap="orderMap">
            select o.id oid, ordernumber, orderprice, customer_id, c.id cid, name, age
            from orders o inner join customer c on c.id = o.customer_id
            where o.id = #{id};
        select>
        
        <resultMap id="orderMap" type="orders">
            <id property="id" column="oid"/>
            <result property="orderNumber" column="orderNumber"/>
            <result property="orderPrice" column="orderPrice"/>
    
            
            <association property="customer" javaType="customer">
                <id property="id" column="cid"/>
                <result property="name" column="name"/>
                <result property="age" column="age"/>
            association>
        resultMap>
    mapper>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    image-20220812215757916

    4.3 一对一关联

    一个班级只有一个授课老师,一个老师也只为一个班级授课。

    image-20220812221548442

    4.4 多对多关联

    总结:无论是什么关联关系,如果某方持有另一方的集合,则使用标签完成映射,如果某方持有另一方的对象,则使用标签完成映射。

    5. 事务

    多个操作同时完成,或同时失败称为事务处理。事务有四个特性(ACID):一致性,持久性,原子性,隔离性。

    下订单的业务:

    • 订单表中完成增加一条记录的操作;
    • 订单明细表中完成N条记录的增加;
    • 商品数据更新(减少);
    • 购物车中已支付商品删除;
    • 用户积分更新(增加);

    在MyBatis框架中设置事务:

     // 程序员自己控制处理的提交和回滚
     <transactionManager type="JDBC"></transactionManager>  
    
    • 1
    • 2

    可以在创建SqlSession对象的时候设置事务:

    image-20220813100334565

    sqlSession = factory.openSession(); =>默认是手工提交事务,设置为false也是手工提交事务,如果设置为true,则为自动提交。

    sqlSession = factory.openSession(true); =>设置为自动提交,在增删改后不需要commit();

    6. 缓存

    MyBatis框架提供两级缓存,一级缓存和二级缓存。默认开启一级缓存;缓存就是为了提交查询的效率。

    使用缓存后,查询的流程:

    image-20220813102527752

    查询时先到缓存里查,如果没有则查询数据库,在数据库查到数据后先将该数据放入缓存,再从缓存当中给返回客户端。下次再查询的时候直接从缓存返回,不再访问数据库。如果数据库中发生commit()操作,则缓存会被全部清空。

    一级缓存使用的是SqlSession的作用域,同一个sqlSession共享一级缓存的数据。

    二级缓存使用的是mapper的作用域,不同的sqlSession只要访问的同一个mapper.xml文件,则共享二级缓存作用域。

    6.1 直接从缓存里面取

    // 测试缓存
    @Test
    public void testCache(){
        User u1 = userMapper.getUserById(1);
        System.out.println("user1: " + u1);
        System.out.println("=======================");
        User u2 = userMapper.getUserById(1);
        System.out.println("user2: " + u2);
        // 直接比较内存地址是否相同,如果相同则是同一份数据,否则不是。
        System.out.println("user1 == user2 : " + (u1 == u2));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    image-20220813161220860

    6.2 缓存被清空

    public void testCache() throws ParseException {
        User u1 = userMapper.getUserById(1);
        System.out.println("user1: " + u1);
    
        System.out.println("=======================");
        // 执行更新操作
        userMapper.update(
                new User(1, "王二五", spf.parse("2020-01-01"), "2", "花溪区大学城"));
        // 执行增删改之后,一定要提交事务
        sqlSession.commit();
    
        User u2 = userMapper.getUserById(1);
        System.out.println("user2: " + u2);
        System.out.println("user1 == user2 : " + (u1 == u2));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image-20220813161816179

    7. 什么是ORM

    ORM(Object Relational Mapping):对象关系映射,MyBatis框架是ORM非常优秀的框架,java语言中以对象的方式操作数据,存到数据库中是以表的方式进行存储,对象中的成员变量与表中的列之间的数据互换称为映射,整个这套操作就是ORM。

    持久化的操作:将对象保存到关系型数据库中 ,将关系型数据库中的数据读取出来以对象的形式封装。

    MyBatis是持久化层优秀的框架。

    8. Mybatis源码分析

    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    
    • 1

    image-20220813205630822

    SqlSession sqlSession = factory.openSession();
    
    • 1

    image-20220813212450568

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    
    • 1

    image-20220813215351989

    userMapper.allUser(); // 待分析
    
    • 1
  • 相关阅读:
    python基于pygame库实现弹幕效果(多行显示,速度不同,颜色不同,循环显示)
    js的增删改查
    数组和List相互转化(摒弃循环)
    Android Studio Giraffe解决gradle reload failed问题
    QT面试必备知识
    生命周期和作用域
    车联网白皮书(2021.12)中国信息通信研究院
    自学Python第二十一天- tkinter 模块,GUI编程
    【matlab学习】现代控制
    2023人工智能十大关键词“Agent”智能体,继ChatGPT后成最热AI焦点
  • 原文地址:https://blog.csdn.net/Hello_super/article/details/126329920