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;
    
  2. 新建maven项目,选quickstart模板

    image-20220809221335835

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

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

    <!--Mybatis依赖-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!--MySQL依赖-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.3</version>
    </dependency>
    
  5. 修改pom.xml文件,添加资源文件指定

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory><!--扫描Java目录下的所有指定文件-->
                <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory><!--所在的目录-->
                <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
    
  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
    
  8. 添加SqlMapConfig.xml文件,MyBatis的核心配置文件

    Mybatis核心配置文件头:

    <?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>
        
    </configuration>
    

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

    <properties resource="jdbc.properties"/>
    

    配置环境:

    <!--environments 的default属性指定以下的多个environment中那一个environment生效。-->
    <environments default="development">
        <!--注意:这里的environment可以拥有多一个,不同的environment之间使用不同的id进行区分。-->
        <environment id="development">
            <!--
            transactionManager的type属性指定事务的管理类型,需要使用大写:
               JDBC,由程序员自行控制事物控制。
               MANAGED,有容器进行事物的控制。
            -->
            <transactionManager type="JDBC">
            </transactionManager>
            <!--
            dataSource的type属性指定数据源的类型,需要使用大写:
               JNDI:java命名目录接口,在服务器端进行数据库连接池的管理;
               POOLED:使用数据库连接池;
               UNPOOLED:不使用数据库连接池;
            -->
            <dataSource type="POOLED">
                <!--
                数据库连接池的基本配置,底层源码:
                  private String driver;
                  private String url;
                  private String username;
                  private String password;
                    -->
                <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>
    

    注册mapper.xml文件

    <!--注册mapper.xml文件-->
    <!--
       resources:从resource目录下找指定名称的文件进行注册;
       	url:使用绝对路径进行注册;
       	class:动态代理方式下的注册;
        -->
    <mappers>
        <mapper resource="StudentMapper.xml"/>
    </mappers>
    
  9. 创建实体类Student,用来封装数据

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

    mapper文件头:

    <?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标签:

    <!--
    mapper:是整个文件的大标签,用来开始和结束xml文件,其属性有:namespace,指定命名空间(相当于包名),用来区分不同mapper.xml文件中相同的ID属性。
    -->
    <mapper namespace="student">
         
    </mapper>
    

    查询所有的学生信息:

    <!--
    完成查询全部学生的功能:
        以前使用JDBC的方式为:List<Student> getAll();
        现在使用Mybatis后,方式变得很简单,只需要配置属性即可:
            id: 用来区分不同的的标签,相当于方法名;
            resultType:指定查询返回的结果集的类型,如果是集合,则必须为该集合指定泛型,且必须是包全名。
            parameterType:如果有参数,则通过它来指定参数的类型;
    -->
    <select id="getAll" resultType="com.xuan.pojo.Student">
        select id, name, email, age from student;
    </select>
    

    按主键id查询学生信息

    <!--
    Student getById(Integer id);
    -->
    <select id="getById" parameterType="int" resultType="com.xuan.pojo.Student">
        select id, name, email, age from student where id = #{id}
    </select>
    

    按学生姓名进行查询

    <!-- 
    List<Student> getByName(String name);
    -->
    <select id="getByName" parameterType="string" resultType="com.xuan.pojo.Student">
        select id, name, email, age from student where name like #{name}
    </select>
    

    添加学生信息

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

    <!--
    insert(Student student);
    
        Student POJO:
            private String name;
            private String email;
            private Integer age;
    
        由于这里的参数类型是Student类型,在下面的values中,只需要按照需要写上Student类型的属性名即可,然后在调用方法时会自动为该参数赋值。
    -->
    <insert id="insert" parameterType="com.xuan.pojo.Student">
        insert into student(name, email, age) values (#{name}, #{email}, #{age})
    </insert>
    

    删除学生

    <!--
    delete(Integer id);
    -->
    <delete id="delete" parameterType="int">
        delete from student where id = #{id}
    </delete>
    

    删除学生

    <!--
    update(Integer id);
    -->
    <update id="update" parameterType="com.xuan.pojo.Student">
        update student set name = #{name}, email = #{email}, age = #{age} where id = #{id}
    </update>
    
  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.3 MyBatis对象分析

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

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

SqlSessionFactory接口

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

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

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

image-20220809223232748

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

SqlSession接口image-20220809222655276

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

1.4 补充

为实体类注册别名:

<!--为实体类注册别名-->
<typeAliases>
    <!--为单个类注册别名-->
    <!--<typeAlias type="com.xuan.pojo.Student" alias="student"/>-->

    <!--
    使用批量注册别名(推荐使用):
    这里会为指定包下的所有类取别名,规则为类名首字母小写的驼峰命名法。
    -->
    <package name="com.xuan.pojo"/>
</typeAliases>

设置日志输出:

<!--设置日志输出底层执行的sql语句-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

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;

注册mapper.xml文件

 <!--注册mapper.xml文件-->
<mappers>
    <!--绝对路径注册-->
    <mapper url="/"></mapper>
    <!--非动态代理方式下的注册-->
    <mapper resource="StudentMapper.xml"></mapper>
    <!--动态代理方式下的单个mapper.xml文件注册-->
    <mapper class="com.bjpowernode.mapper.UsersMapper"></mapper>
    <!--批量注册(推荐使用)-->
    <package name="com.bjpowernode.mapper"></package>
</mappers> 

测试类

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");
    }
}

2.3 #{}与${}

#{}占位符:

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

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

    <!--
    User getUserByUsername(String username);
    -->
    <select id="getUserByUsername" resultType="user" parameterType="string">
        select id, username, birthday, sex, address from users where username like #{username}
    </select>
    
  • 如果parameterType的类型是实体类的类型,则#{}里只能是类中成员变量的名称,而且区分大小写;

    <!--
    更新用户
    在xml文件中增删改不需要写返回值。
    
    User实体类:
        private Integer id;
        private String username;
        private Date birthday;
        private String sex;
        private String address;
    -->
    <update id="update" parameterType="user">
        update users set username = #{username}, birthday = #{birthday}, sex = #{sex}, address = #{address}
        where id = #{id}
    </update>
    

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

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

    • A. 如果parameterType的类型是简单类型,则${}里随便写,但是分版本,如果是3.5.1及以下的版本,只能写value;
    ```xml
    <select id="getByName" parameterType="string" resultType="users">  ===>入参是简单类型
        select id,username,birthday,sex,address
        from users
        where username like '%${zar}%'   ===>随便写
    </select>
    ```
    
    • 如果parameterType的类型是实体类的类型,则${}里只能是类中成员变量的名称(现在已经少用);

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

      <!--
      List<User> getUserByUsernameBetter(String username);
      这种貌似更专业一点。但是本质上还是字符串的拼接,可以有效抵御SQL注入问题。
      -->
      <select id="getUserByUsername" resultType="user" parameterType="string">
          select id, username, birthday, sex, address from users where username like concat('%', #{username}, '%')
      </select>
      
  • 字符串替换(使用的还有点多)

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

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

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

/**
 * 通过列名与列值获取对应的用户信息。这里@Param参数value可以与被修饰的参数名不一样,但是规范要求一样。
 * @param columnName 对应表的列的名称,为了能够在xml文件中正确的取到该值,这两个参数必须使用@Param注解进行修饰。
 * @param columnValue 对应表的列的值。
 * @return 返回查询的用户的信息。
 */
List<User> getUserByUsernameOrAddr(
        @Param("columnName")
        String columnName,
        @Param("columnValue")
        String columnValue
);
<!--
List<User> getUserByUsernameOrAddr(
        @Param("columnName")
        String columnName,
        @Param("columnValue")
        String columnValue);
当参数有多个时,可以不写。
注意:这里的列名必须使用${}进行接收,在预编译sql语句的时候才能够将对应的列名解释出来:
where ${columnName} like concat('%', #{columnValue}, '%')
-->
<select id="getUserByUsernameOrAddr" resultType="user">
    select id, username, birthday, sex, address
    from users
    where ${columnName} like concat('%', #{columnValue}, '%')
</select>

2.4 返回主键值

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

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

image-20220810211424134

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

/**
 * 新增用户,添加之后返回用户的id。
 * @param user 添加的对象;
 * @return 返回影响的行数;
 */
int insert(User user);
<!--
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>
@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);
}

2.4 UUID

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

// UUID
@Test
public void testUUID(){
    UUID uuid = UUID.randomUUID();
    System.out.println(uuid.toString().replace("-", ""));
}

3. 动态sql

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

3.1 代码片段

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

    <!--定义代码片段-->
    <sql id="allColumns">
        id, username, birthday, sex, address
    </sql>
    
  • :用来引用定义的代码片断;

    <select id="allUsers" resultType="user">
        select <include refid="allColumns"/> from users
    </select>
    

3.2 逻辑判断

  • :进行条件判断;

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

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

    /**
     * 进行多条件查询。
     * @param user 传入一个对象,判断对象是否为null,如果不为空,则该属性值即为查询条件之一。
     * @return 查询的对象;
     */
    List<User> moreConditionQuery(User user);
    
    <!--
    使用动态sql进行多条件查询:
    List<User> moreConditionQuery(User user);
    
    提示:字符串类型的字段需要判断null和'',而日期类型的字段只需要判断是否为null即可。
    
    select id, username, birthday, sex, address
        from users
        where 1 = 1  // 动态sql中<where></where>标签的本质就是相当于这里1=1的占位符。
              and username like '%陈%'
              and sex = '1';
    -->
    <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>
    
    // 多条件查询
    @Test
    public void testMoreConditionQuery(){
        // 默认为查询所有的用户
        User user = new User();
        // 查询用户名中包含“小”的用户
        user.setUsername("小");
        // 并且查询所在地址中包含“市”的字
        user.setAddress("市");
        List<User> userList = userMapper.moreConditionQuery(user);
        userList.forEach(System.out::println);
    }
    

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

    image-20220811144822399

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

    image-20220811145043222

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

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

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

3.3 更新操作

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

image-20220811150927768

image-20220811151049487

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

/**
 * 选择性的进行更新操作。
 * @param user 更新的用户;
 * @return 返回影响行数;
 */
int conditionUpdate(User user);
<!--
int conditionUpdate(User user);

增删改的返回结果不用写。

<set>标签:
    这里面可以使用<if>标签进行逻辑的判断,在每个属性的赋值后都要加上一个逗号进行收尾。
    因为我们不知道哪个属性是最后一个,但是Mybatis是知道的,它会自动进行判断。
    如果是最后一个字段,则该字段后面的逗号会自动被删除。
-->
<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>
// 选择更新
@Test
public void testConditionUpdate(){
    User user = new User();
    user.setId(9);
    user.setUsername("张宇");
    userMapper.conditionUpdate(user);
    sqlSession.commit();
}

底层执行的sql语句为:

image-20220811153126438

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

image-20220811173748760

3.4 遍历操作

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

查询实现:

<!--
List<User> queryMoreUserById(Integer[] ids);

这里由于传入的是数组类型,所以在select标签的属性里面不用写array。

foreach标签个属性:
    collection: 固定值为:map、list、array,即看传入的参数是什么类型,这里就选择对应的类型就好了;
    item:从容器中取出的每一项的临时变量,命名随意;
    separator:取出的每一项之间的分隔符;
    open:包裹foreach之前的符号;
    close:包裹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>
<!--
批量删除
int batchDel(Integer[] ids);
-->
<delete id="batchDel">
    delete from users
    where id in
    <foreach collection="array" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>
<!--
// 批量增加
void batchInsert(List<User> users);

由于上面对应方法的参数是List类型,所以<insert>标签中的parameterType属性的值可以不用声明,当然声明也可以(这里需要声明为user)。
对应的sql语句:
insert into users(username, birthday, sex, address) values
    ('a', '2002-01-02', '2', '花溪a'),
    ('b', '2002-01-02', '2', '花溪b'),
    ('c', '2002-01-02', '2', '花溪c');

注意:由于这里的foreach遍历出来的变量是user对象,所以使用对象.属性的方式使用属性值。
-->
<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>

3.5 指定参数位置

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


/*
一个方法有多个参数,且这些参数无法通过一个对象进行封装。这里有两种解决方案(不推荐使用):
    1. 使用@Param()注解;
    2. 使用#{args0},#{args1}...
 */
// 查询指定日期出生的用户
List<User> queryByBirthday(Date beginDate, Date endDate);
<!--
List<User> queryByBirthday(Date beginDate, Date endDate);
-->
<select id="queryByBirthday" resultType="user">
    select <include refid="allColumns"/>
    from users
    where birthday between #{arg0} and #{arg1}
</select>
@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);
}

3.6 入参是Map(重点掌握)

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

// 使用Map对上面的方法进行优化(推荐使用,也是应该重点掌握的)
List<User> queryByBirthdayMap(Map<String, Date> map);
<!--
List<User> queryByBirthdayMap(Map<String, Date> map);

这里使用Map时,直接使用#{key}的方式即可将Map中的数据取出;
-->
<select id="queryByBirthdayMap" resultType="user">
    select <include refid="allColumns"/>
    from users
    where birthday between #{beginDate} and #{endDate}
</select>
@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);
}

3.7 返回值是Map

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

<!--
只返回一个结果的情况。

Map<Object, Object> queryReturnMap(Integer id);
-->
<select id="queryReturnMap" resultType="map" parameterType="int">
    select username, address
    from users
    where id = #{id}
</select>
<!--
List<Map<Object, Object>> queryMulMap();
-->
<select id="queryMulMap" resultType="map">
    select username, address
    from users
</select>

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;

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

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

image-20220812171200508

public class Book {
    private Integer id;
    private String name;
    
    // ... gettter and setter and constructor and toString()
}
<!--
// 获取所有的
List<Book> allBook();

这里给sql语句取别名。
-->
<select id="allBook" resultType="book">
    select bookid id, bookname name from book
</select>
b>方案二(推荐使用)
<!--
使用resultMap手工完成映射。

resultMap的type属性:表示需要进行映射的类的属性,可以理解为需要返回的类型。
property:类中的属性名称,区分大小写;
column:表中的列名,不区分大小写;
-->
<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>

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;

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

image-20220812211621539

<!--
// 通过用户id查询指定用户以及该用户所有的订单
Customer allCustomer(Integer id);

Customer实体类:
    private Integer id;
    private String name;
    private short age;
    // 一个客户对应手中有多个订单
    private List<Orders> ordersList;
-->
<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>
<!--这里的type值可以理解为返回的对象的类型。-->
<resultMap id="customerMap" type="customer">
    <!--主键-->
    <id property="id" column="cid"/>
    <!--非主键-->
    <result property="name" column="name"/>
    <result property="age" column="age"/>
    <!--
    特殊类型(难点),对象属性是集合。
        private Integer id;
        private String orderNumber;
        private Double orderPrice;
    -->
    <collection property="ordersList" ofType="orders">
        <!--主键-->
        <id property="id" column="oid"/>
        <!--非主键-->
        <result property="orderNumber" column="orderNumber"/>
        <result property="orderPrice" column="orderPrice"/>
    </collection>
</resultMap>

4.2 多对一关联关系.

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

image-20220812211551551

package com.xuan.mapper;

import com.xuan.pojo.Orders;

public interface OrdersMapper {
    // 查询指定的订单包括下单的用户,注:为了简单起见,这里的用户只有一个,即:订单与用户为多对一的关系。
    Orders getOrderById(Integer id);
}
<mapper namespace="com.xuan.mapper.OrdersMapper">
    <!--
    Orders getOrderById(Integer id);
    多表查询,查询每个订单对应的用户。
    注意:这里是多对一的情况,即订单和客户的关系。这里查询的sql要注意一个问题,就是订单不会无中生有,一定是有客户下单了才会产生订单,
    即:有订单的前提是一定有对应的用户存在。所以,在以下的sql中只能写内连接后右连接。
    -->
    <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>
    <!--
    Orders实体类:
        private Integer id;
        private String orderNumber;
        private Double orderPrice;

        // 多的一方持有一方的对象
        private Customer customer;
    -->
    <resultMap id="orderMap" type="orders">
        <id property="id" column="oid"/>
        <result property="orderNumber" column="orderNumber"/>
        <result property="orderPrice" column="orderPrice"/>

        <!--
        处理关联的属性customer

        javaType:指定customer属性的参数类型,这里可以使用别名。

        Customer实体类:
            private Integer id;
            private String name;
            private short age;

            // 在这里这个参数不用指定。
            private List<Orders> ordersList;
        -->
        <association property="customer" javaType="customer">
            <id property="id" column="cid"/>
            <result property="name" column="name"/>
            <result property="age" column="age"/>
        </association>
    </resultMap>
</mapper>

image-20220812215757916

4.3 一对一关联

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

image-20220812221548442

4.4 多对多关联

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

5. 事务

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

下订单的业务:

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

在MyBatis框架中设置事务:

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

可以在创建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));
}

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));
}

image-20220813161816179

7. 什么是ORM

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

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

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

8. Mybatis源码分析

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

image-20220813205630822

SqlSession sqlSession = factory.openSession();

image-20220813212450568

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

image-20220813215351989

userMapper.allUser(); // 待分析
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值