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('张三','[email protected]',22);
    insert into student(name,email,age) values('李四','[email protected]',21);
    insert into student(name,email,age) values('王五','[email protected]',22);
    insert into student(name,email,age) values('赵六','[email protected]',24);
    select * from student;
    
  2. 新建maven项目,选quickstart模板

    bjpowernode_MyBatis_第1张图片

  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>
    
  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>
    
  6. 在idea中添加数据库的可视化,主要是方便数据管理

    bjpowernode_MyBatis_第2张图片

  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核心配置文件头:

    
    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="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>
    

    注册mapper.xml文件

    
    
    <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文件头:

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

    mapper标签:

    
    <mapper namespace="student">
         
    mapper>
    

    查询所有的学生信息:

    
    <select id="getAll" resultType="com.xuan.pojo.Student">
        select id, name, email, age from student;
    select>
    

    按主键id查询学生信息

    
    <select id="getById" parameterType="int" resultType="com.xuan.pojo.Student">
        select id, name, email, age from student where id = #{id}
    select>
    

    按学生姓名进行查询

    
    <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 id="insert" parameterType="com.xuan.pojo.Student">
        insert into student(name, email, age) values (#{name}, #{email}, #{age})
    insert>
    

    删除学生

    
    <delete id="delete" parameterType="int">
        delete from student where id = #{id}
    delete>
    

    删除学生

    
    <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", "[email protected]", 20));
    
            // 修改
            // sqlSession.update("student.update", new Student(5, "babql", "[email protected]", 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);

bjpowernode_MyBatis_第3张图片

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

SqlSession接口image-20220809222655276

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

1.4 补充

为实体类注册别名:


<typeAliases>
    
    

    
    <package name="com.xuan.pojo"/>
typeAliases>

设置日志输出:


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

2. 动态代理存在意义

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

bjpowernode_MyBatis_第4张图片

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文件

 
<mappers>
    
    <mapper url="/">mapper>
    
    <mapper resource="StudentMapper.xml">mapper>
    
    <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,则#{}里面可以随便写;

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

    
    <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
    
    ```
    
    • 如果parameterType的类型是实体类的类型,则${}里只能是类中成员变量的名称(现在已经少用);

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

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

<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。下面来解决这个问题。

bjpowernode_MyBatis_第5张图片

在插入语句结束后, 返回自增的主键值到入参的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);
    
    
    <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为:

    bjpowernode_MyBatis_第6张图片

    当用户属性有一个不为空时,这里以用户名为小作为查询条件。执行的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

bjpowernode_MyBatis_第7张图片

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

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

<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

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

bjpowernode_MyBatis_第8张图片

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>

<delete id="batchDel">
    delete from users
    where id in
    <foreach collection="array" item="id" separator="," open="(" close=")">
        #{id}
    foreach>
delete>

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

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

<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语句的列名或别名。


<select id="queryReturnMap" resultType="map" parameterType="int">
    select username, address
    from users
    where id = #{id}
select>

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

<select id="allBook" resultType="book">
    select bookid id, bookname name from book
select>

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>

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;

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

bjpowernode_MyBatis_第9张图片


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

4.2 多对一关联关系.

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

bjpowernode_MyBatis_第10张图片

package com.xuan.mapper;

import com.xuan.pojo.Orders;

public interface OrdersMapper {
    // 查询指定的订单包括下单的用户,注:为了简单起见,这里的用户只有一个,即:订单与用户为多对一的关系。
    Orders getOrderById(Integer id);
}
<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>

image-20220812215757916

4.3 一对一关联

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

bjpowernode_MyBatis_第11张图片

4.4 多对多关联

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

5. 事务

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

下订单的业务:

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

在MyBatis框架中设置事务:

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

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

bjpowernode_MyBatis_第12张图片

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

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

6. 缓存

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

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

bjpowernode_MyBatis_第13张图片

查询时先到缓存里查,如果没有则查询数据库,在数据库查到数据后先将该数据放入缓存,再从缓存当中给返回客户端。下次再查询的时候直接从缓存返回,不再访问数据库。如果数据库中发生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));
}

bjpowernode_MyBatis_第14张图片

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

bjpowernode_MyBatis_第15张图片

7. 什么是ORM

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

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

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

8. Mybatis源码分析

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

bjpowernode_MyBatis_第16张图片

SqlSession sqlSession = factory.openSession();

bjpowernode_MyBatis_第17张图片

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

bjpowernode_MyBatis_第18张图片

userMapper.allUser(); // 待分析

你可能感兴趣的:(Spring,mybatis,java,数据库,sql)