Mybatis详解

Mybatis

环境:

  • JDK1.8
  • Mysql 5.7
  • maven 3.6.1
  • IDEA

回顾:

  • JDBC
  • Mysql
  • Java基础
  • maven
  • Junit

框架:都有配置文件的。最好的方式:看官网文档

https://mybatis.org/mybatis-3/zh/

1、简介

1.1 什么是Mybatis

  • MyBatis 是一款优秀的持久层框架

  • 它支持自定义 SQL、存储过程以及高级映射。

  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

  • MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis。2013年11月迁移到Github

  • iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。

如何获得Mybatis?

  • maven仓库:https://mvnrepository.com/

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.10</version>
    </dependency>
    
  • Github:https://github.com/mybatis/mybatis-3/releases

  • 中文文档:https://mybatis.org/mybatis-3/zh/

1.2 持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程

  • 内存:断电即失

  • 数据库(jdbc),io文件持久化

  • 生活:冷藏

为什么需要持久化?

  • 有一些对象不能让它丢掉
  • 内存太贵了

1.3 持久层

Dao层,Service层,Controller层 …

  • 完成持久化工作的代码块
  • 层界限十分明显

1.4 为什么需要Mybatis?

  • 帮助程序员将数据存入数据库中

  • 方便

  • 传统的JDBC代码太复杂了。简化。框架。自动化

  • 不用Mybatis也可以。更容易上手。技术没有高低之分

优点:

  • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

  • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

  • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

  • 提供映射标签,支持对象与数据库的orm字段关系映射。

  • 提供对象关系映射标签,支持对象关系组建维护。

  • 提供xml标签,支持编写动态sql

最重要的一点:使用的人多!

2、第一个Mybatis程序

思路:搭建环境——》导入Mybatis——》编写代码——》测试

2.1 搭建环境

  1. 搭建数据库
CREATE DATABASE if NOT EXISTS `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
  `id` INT(10) NOT NULL COMMENT '用户id',
	`name` VARCHAR(50) NOT NULL COMMENT '用户名',
	`pwd` VARCHAR(50) NOT NULL COMMENT '密码',
	PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`pwd`) 
VALUES(1,'张三','123456'),
(2,'李四','123456'),
(3,'王五','123456');
  1. 在IDEA新建项目(父工程)

    image-20220731185146469

  2. 删除src目录

  3. 导入maven依赖

    <!--导入依赖-->
    <!--1、mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--2、mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.10</version>
    </dependency>
    <!--3、junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    

2.2 创建一个模块(子工程)

image-20220731185338805

  • 编写mybstis核心配置文件:mybatis-config.xml

    <?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>
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=UTC"/>
                    <property name="username" value="root"/>
                    <property name="password" value="123456"/>
                </dataSource>
            </environment>
        </environments>
    </configuration>
    

    image-20220731185405483

  • 编写mybatis工具类

package com.shu.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

//工具类  SqlSessionFactory ——>SqlSession
public class MybatisUtils {
    //提升作用域
    public static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //使用mybatis第一步:获取SqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
    // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
    public static SqlSession getSqlSession(){
        /* 
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
        */
        
        //优化代码
        return sqlSessionFactory.openSession();
    }
}

2.3 编写代码

  • 实体类

    package com.shu.pojo;
    
    //实体类
    public class user {
        private int id;
        private String name;
        private String pwd;
    
        public user() {
        }
    
        public user(int id, String name, String pwd) {
            this.id = id;
            this.name = name;
            this.pwd = pwd;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPwd() {
            return pwd;
        }
    
        public void setPwd(String pwd) {
            this.pwd = pwd;
        }
    
        @Override
        public String toString() {
            return "user{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
    
  • Dao接口

    package com.shu.dao;
    
    import com.shu.pojo.User;
    
    import java.util.List;
    
    //UserDao等价于Mapper
    public interface UserDao {
        //返回值类型:User(获取用户)
        List<User> getUserList();  //获取所有用户
    }
    
  • 接口实现类UserMapper.xml:由原来的Impl转换为一个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">
    
    <!--namespace:绑定一个对应的Dao/Mapper接口-->
    <mapper namespace="com.shu.dao.UserDao">
        <!--select查询语句-->
        //id="getUserList":接口中的方法名      resultType="com.shu.pojo.User":返回类型
        <select id="getUserList" resultType="com.shu.pojo.User">
            select * from mybatis.user
        </select>
    </mapper>
    

提示对命名空间的一点补充

在之前版本的 MyBatis 中,**命名空间(Namespaces)**的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名空间的作用有两个,一个是利用更长的全限定名来将不同的语句隔离开来,同时也实现了你上面见到的接口绑定。就算你觉得暂时用不到接口绑定,你也应该遵循这里的规定,以防哪天你改变了主意。 长远来看,只要将命名空间置于合适的 Java 包命名空间之中,你的代码会变得更加整洁,也有利于你更方便地使用 MyBatis。

**命名解析:**为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。

  • 全限定名(比如 “com.mypackage.MyMapper.selectAllThings)将被直接用于查找及使用。
  • 短名称(比如 “selectAllThings”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.foo.selectAllThings” 和 “com.bar.selectAllThings”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。

2.4 测试

  • 编写进行junit测试(会报错)

    package com.shu.dao;
    
    import com.shu.pojo.User;
    import com.shu.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void test(){
            //第一步:获取SqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            //方式一:getMapper(执行SQL)  最新推荐!!!
            /*UserDao userDao = sqlSession.getMapper(UserDao.class);
            List<User> userList = userDao.getUserList();*/
    
            //方式二:   原来的,不推荐使用!
            //List<Object> objects = sqlSession.selectList("com.shu.dao.UserDao.getUserList");
            //对上面代码进行类型转换(强转)
            List<User> userList = sqlSession.selectList("com.shu.dao.UserDao.getUserList");
    
            for (User user : userList) {
                System.out.println(user);
            }
    
            //关闭SqlSession
            sqlSession.close();
        }
    }
    
    //官方建议
    package com.shu.dao;
    
    import com.shu.pojo.User;
    import com.shu.utils.MybatisUtils;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;
    
    import java.util.List;
    
    public class UserDaoTest {
        @Test
        public void test(){
            //第一步:获取SqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
    
            try{
                //方式一:getMapper(执行SQL)  最新推荐!!!
                /*UserDao userDao = sqlSession.getMapper(UserDao.class);
                List<User> userList = userDao.getUserList();*/
    
                //方式二:   原来的,不推荐使用!
                //List<Object> objects = sqlSession.selectList("com.shu.dao.UserDao.getUserList");
                //对上面代码进行类型转换(强转)
                List<User> userList = sqlSession.selectList("com.shu.dao.UserDao.getUserList");
    
                for (User user : userList) {
                    System.out.println(user);
                }
            }catch(Exception e){
                e.printStackTrace();
            }finally {
                //关闭SqlSession
                sqlSession.close();
            }
        }
    }
    
    

image-20220731190100217

报错1:配置文件没有注册问题

org.apache.ibatis.binding.BindingException: Type interface com.shu.dao.UserDao is not known to the MapperRegistry.

MapperRegistry是什么?

解决方案:在mybatis-config.xml文件中加入MapperRegistry部分代码

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    
    <!--每一个Mapper.xml都需要在Mybatis的核心配置文件中注册            MapperRegistry       -->
    <mappers>
        <mapper resource="com/shu/dao/UserMapper.xml"/>
    </mappers>
    
</configuration>

报错2:

maven报错:maven导出资源问题

java.lang.ExceptionInInitializerError
	at com.shu.dao.UserDaoTest.test(UserDaoTest.java:14)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: org.apache.ibatis.exceptions.PersistenceException: 
### Error building SqlSession.
### The error may exist in com/shu/dao/UserMapper.xml
### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/shu/dao/UserMapper.xml
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:82)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:66)
	at com.shu.utils.MybatisUtils.<clinit>(MybatisUtils.java:21)
	... 23 more
Caused by: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/shu/dao/UserMapper.xml
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:122)
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.parse(XMLConfigBuilder.java:99)
	at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:80)
	... 25 more
Caused by: java.io.IOException: Could not find resource com/shu/dao/UserMapper.xml
	at org.apache.ibatis.io.Resources.getResourceAsStream(Resources.java:114)
	at org.apache.ibatis.io.Resources.getResourceAsStream(Resources.java:100)
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.mapperElement(XMLConfigBuilder.java:378)
	at org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XMLConfigBuilder.java:120)
	... 27 more

解决方案:在父工程的pom.xml文件中加入以下代码

<!--maven资源过滤问题:在build中配置resources,来防止我们资源导出失败的问题-->
<build>
<resources>
    <resource>
        <directory>src/main/resources</directory>
        <includes>
            <include>**/*.properties</include>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
    <resource>
        <directory>src/main/java</directory>
        <includes>
             <include>**/*.properties</include>
             <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>
</build>

报错3:编码不一致问题

org.apache.ibatis.builder.BuilderException: Error creating document instance.

这个问题的主要原因是xml文件中声明的编码与xml文件本身保存时的编码不一致。 比如:你的声明是

<?xml version="1.0" encoding="UTF-8"?>

但是却以ANSI格式编码保存,尽管并没有乱码出现,但是xml解析器是无法解析的。 解决办法就是重新设置xml文件保存时的编码与声明的一致。

ps:pom.xml添加代码若运行成功后,删除添加的代码,依然能再次运行成功!

解决方案:在pom.xml文件中添加如下代码:

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
  • 避坑之后,测试成功

    image-20220731190314237

总结:

image-20220731193534642

3、CRUD

3.1 namespace

namespace中的包名要和Dao/Mapper接口中的包名保持一致!

3.2 select

选择,查询语句

  • id:就是对应的namespace中的方法名
  • resultType:SQL语句执行的返回值!
  • parameterType:参数类型
  1. 编写接口

    //根据id查询用户
    User getUserById(int id);
    
  2. 编写对应的UserMapper.xml中的sql语句

    <!--根据id查询用户-->
    <select id="getUserById" resultType="com.shu.pojo.User" parameterType="int">
        select * from mybatis.user where id=#{id}
    </select>
    
  3. 测试

    /*
    根据id查询用户
    */
    @Test
    public void getUserByIdTest(){
        //获取执行sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //获取接口
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(2);
        System.out.println(user);
    
        //关闭sqlSession
        sqlSession.close();
    }
    

3.3 insert

  1. 编写接口

    //insert一个用户
    int addUser(User user);
    
  2. 编写对应的UserMapper.xml中的sql语句

    <!--insert一个用户-->
    <insert id="addUser" parameterType="com.shu.pojo.User">
        insert into mybatis.user (id,name,pwd) value (#{id},#{name},#{pwd})
    </insert>
    
  3. 测试

    /*
    insert一个用户
    注意:增删改需要提交事务
    */
    @Test
    public void addUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //User user = new User(4, "小舒", "123456");
    
        int res = mapper.addUser(new User(4, "小舒", "123456"));
        if(res>0){
            System.out.println("插入成功!");
        }
    
        //提交事务,不提交则不会持久化到数据库
        sqlSession.commit();
    
        sqlSession.close();
    }
    

3.4 update

  1. 编写接口

    //修改一个用户
    int updateUser(User user);
    
  2. 编写对应的UserMapper.xml中的sql语句

    <!--update一个用户-->
    <update id="updateUser" parameterType="com.shu.pojo.User">
        update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
    </update>
    
  3. 测试

    /*
    update一个用户
    注意:增删改需要提交事务
    */
    @Test
    public void updateUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
        int res = mapper.updateUser(new User(4, "xiaoshu", "123456"));
        if(res>0){
            System.out.println("更新成功!");
        }
        //提交事务,不提交则不会持久化到数据库
        sqlSession.commit();
    
        sqlSession.close();
    }
    

3.5 delete

  1. 编写接口

    <!--删除一个用户-->
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id}
    </delete>
    
  2. 编写对应的UserMapper.xml中的sql语句

    <!--删除一个用户-->
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id=#{id}
    </delete>
    
  3. 测试

    /*
    删除一个用户
     */
    @Test
    public void deleteUser(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int res = mapper.deleteUser(4);
        if(res>0){
            System.out.println("删除成功!");
        }
        sqlSession.commit();
        sqlSession.close();
    }
    

注意点:

  • 增删改需要提交事务
  • 标签不要匹配错
  • resource绑定mapper,需要使用路径!
  • 程序配置文件必须符合规范
  • maven资源导出问题

3.6 万能的Map

假设我们的实体类,或者数据库中的表、字段或者参数过多,我们应当考虑使用Map!

//万能的Map
int addUser2(Map<String,Object> map);
<!--万能的Map-->
<insert id="addUser2" parameterType="map">
    insert into mybatis.user (id,name,pwd) value (#{userid},#{userName},#{passWord})
</insert>
@Test
public void addUser2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("userid",5);
    map.put("userName","小舒");
    map.put("passWord","123456");
    int res = mapper.addUser2(map);
    if(res>0){
        System.out.println("插入成功!");
    }

    //提交事务,不提交则不会持久化到数据库
    sqlSession.commit();

    sqlSession.close();
}

Map传递参数,直接在sql中取出key即可! 【parameterType=“map”】

对象传递参数,直接在sql中取对象的属性即可! 【parameterType=“object”】

只有一个基本类型参数的情况下,可以直接在sql中取到!

多个参数用Map,或者注解!

3.7 思考题

模糊查询怎么写?

//模糊查询一个用户
List<User> getUserLike(String value);
<!--模糊查询一个用户-->
<select id="getUserLike" resultType="com.shu.pojo.User">
    select * from mybatis.user where name like #{value}
</select>
<!--模糊查询一个用户-->
@Test
public void getUserLike(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getUserLike("%李%");
    for (User user : userList) {
        System.out.println(user);
    }

    sqlSession.close();
}
  1. java代码执行的时候,传递通配符%

    List<User> userList = mapper.getUserLike("%李%");
    
  2. 在sql拼接中使用通配符!

    select * from mybatis.user where name like "%"#{value}*"%"
    

4、配置解析

4.1 核心配置文件

  • mybatis-config.xml
  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
//在xml中,所有的标签都可以规定其顺序
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

image-20220821114356935

4.2 环境配置(environments)

MyBatis 可以配置成适应多种环境,学会使用多套运行环境!

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

Mybatis默认的事务管理器就是JDBC,连接池:POOLED

4.3 属性(properties)

我们可以通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。【db.properties】

编写一个配置文件db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username=root
password=123456

在核心配置文件中引入

<!--引入外部配置文件:db.properties直接在resource路径下就不用写路径了-->
<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="111111"/>
</properties>
  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件的!

4.4 类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

<!--可以给实体类起别名-->
<typeAliases>
    <typeAlias type="com.shu.pojo.User" alias="User"/>
</typeAliases>
<!--获取所有用户-->
<select id="getUserList" resultType="User">
    select * from mybatis.user
</select>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

扫描实体类的包,它的默认别名就为这个类的类名,首字母小写!

<!--可以给实体类起别名-->
<typeAliases>
    <package name="com.shu.pojo"/>
</typeAliases>
<!--获取所有用户-->
<select id="getUserList" resultType="user">
    select * from mybatis.user
</select>

在实体类比较少的时候,使用第一种方式

如果实体类十分多,建议使用第二种!

区别:

第一种可以DIY(自定义别名),第二种则不行(没有注解的情况下;有注解的情况下可以通过注解指定别名)

<!--可以给实体类起别名-->
<typeAliases>
    <package name="com.shu.pojo"/>
</typeAliases>
@Alias("hello")
public class User {
    ...
}
<!--获取所有用户-->
<select id="getUserList" resultType="hello">
    select * from mybatis.user
</select>

4.5 设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

image-20220821133555812

image-20220821133509855

4.6 其他配置

typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)

  • MyBatis Generator Core
  • MyBatis Plus:mybatis的增强工具
  • 通用Mapper

4.7 映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

方式一:【推荐使用】大多情况下,写一个注册绑定一个

<!--每一个Mapper.xml都需要在Mybatis的核心配置文件中注册-->
<mappers>
    <mapper resource="com/shu/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件绑定注册

<!--每一个Mapper.xml都需要在Mybatis的核心配置文件中注册-->
<mappers>
    <mapper class="com.shu.dao.UserMapper"/>
</mappers>

注意:UserMapper和UserMapper.xml必须同名,并且需要在一个包下,否则会报错

image-20220821135232466

方式三:使用扫描包进行绑定注入

<!--每一个Mapper.xml都需要在Mybatis的核心配置文件中注册-->
<mappers>
    <package name="com.shu.dao"/>
</mappers>

注意:UserMapper和UserMapper.xml必须同名,并且需要在一个包下,否则会报错

4.8 作用域(Scope)和生命周期

生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题

image-20220821165555667

SqlSessionFactoryBuilder:

  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。

  • 局部变量

SqlSessionFactory:

  • SqlSessionFactory说白了就是可以想象成:数据库连接池
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession:

  • 连接到连接池的的一个请求!
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完之后需要赶紧关闭,否则资源被占用!

image-20220821165952309

这里面的每一个Mapper,就代表具体的业务!

5、ResultMap结果集映射

解决属性名和字段名不一致的问题

5.1 问题

数据库中的字段

image-20220821170338564

新建一个项目,拷贝之前的,测试实体类字段不一致的情况

//实体类
public class User {
    private int id;
    private String name;
    private String password;
    }

image-20220821171357299

进行功能测试出现问题

@Test
public void getUserList(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    System.out.println(mapper.getUserById(2));
        
    sqlSession.close();
}

image-20220821171817358

select * from mybatis.user where id=#{id}

<!--
类型处理器,完整查询语句:
select id,name,pwd from mybatis.user where id=#{id}
-->

解决方法:

  • 起别名

    <!--根据id查询用户-->
        <select id="getUserById" resultType="com.shu.pojo.User" parameterType="int">
            <!--完整查询语句:select id,name,pwd from mybatis.user where id=#{id}-->
            select id,name,pwd as password from mybatis.user where id=#{id}
        </select>
    

5.2 引出ResultMap结果集映射

结果集映射

id    name    pwd
id    name    password

image-20220821173816261

编写以上代码自行测试即可。

  • resultMap 元素是 MyBatis 中最重要最强大的元素。
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
  • ResultMap 的优秀之处——你完全可以不用显式地配置它们。
  • 如果这个世界总是这么简单就好了。

6、日志

6.1 日志工厂

如果一个数据库操作,出现了异常,我们需要拍错。日志就是最好的助手。

曾经:sout 、debug

现在:日志工厂!

image-20220821224628312

SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

  • STDOUT_LOGGING 【标准日志输出】
  • SLF4J

在mybatis中具体使用哪一个日志实现,在设置中设定!

在mybatis核心配置文件中,配置我们的配置!

<!--设置-->
<settings>
    <!--标准的日志工厂实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

image-20220821230051687

6.2 LOG4J

什么是LOG4J?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件等
  • 我们也可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

1.先导入LOG4J的包

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2.log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger = DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
#定义日志文件输出路径:如log4j.appender.file.File = D:/logs/log.log4j ——》D盘的指定路径下
#当前目录下
log4j.appender.file.File = ./logs/log.log4j

log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3.配置log4j日志的实现

<!--设置-->
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

4.LOG4J的使用,直接测试运行刚才的查询

image-20220821234921861

简单使用

  1. 在要使用Log4j的测试类中,导入包import org.apache.log4j.Logger;
  2. 日志对象,参数为当前类的class
  3. 日志级别
  4. 在logs文件中查看日志

image-20220822000820672

7、分页

思考:为什么要分页?

  • 减少数据的处理量

7.1 使用Limit分页

语法:select *  from mybatis.user limit startIndex,pageSize
-- 从第0个下标开始,每页两个数据
select *  from mybatis.user limit 0,2;

image-20220822101848863

-- 默认从第0个下标开始,每页3个数据
select *  from mybatis.user limit 3;  #[0,n]

image-20220822102544472

使用mybatis实现分页,核心SQL

  1. 接口

    //分页1:需要两个参数,使用万能的Map
    List<User> getUserByLimit(Map<String,Integer> map);
    
  2. Mapper.xml

    <!--实现分页查询-->
    <select id="getUserByLimit" parameterType="map" resultMap="UserMap">
        select * from user limit #{startIndex},#{pageSize}
    </select>
    
  3. 测试

    @Test
    public void getUserByLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
        HashMap<String, Integer> map = new HashMap<>();
        map.put("startIndex",2);
        map.put("pageSize",2);
        List<User> userList = mapper.getUserByLimit(map);//需要传一个map,则在上面构造一个
        for (User user : userList) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }
    

7.2 RowBounds分页

注意:面向对象的RowBounds分页没有使用SQL来的快!

不再使用SQL实现分页

  1. 接口

    //分页2:通过RowBounds实现
    List<User> getUserByRowBounds();
    
  2. Mapper.xml

    <!--分页实现查询:RowBounds-->
    <select id="getUserByRowBounds"  resultMap="UserMap">
        select * from user
    </select>
    
  3. 测试

    @Test
    public void getUserByRowBounds(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
    
        //RowBounds实现
        RowBounds rowBounds = new RowBounds(1, 2);
    
        //通过java代码层面实现分页
        List<User> userList = sqlSession.selectList("com.shu.dao.UserMapper.getUserByRowBounds",null,rowBounds);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
    

7.3 分页插件

image-20220822115001142

了解即可,万一以后公司的架构师说要使用,你需要知道它是什么东西!

8、使用注解开发

8.1、面向接口编程

之前学过面向对象编程,也学习过接口,但在真正的开发中,很多时候会选择面向接口编程。
根本原因:解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

三个面向区别

面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性和方法;
面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现;
接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构;

8.2 使用注解开发

  1. 注解在接口上实现

    @Select("select * from user")
    List<User> getUsers();
    
  2. 需要在核心配置文件中绑定接口

    <!--绑定接口-->
    <mappers>
        <mapper class="com.shu.dao.UserMapper"/>
    </mappers>
    
  3. 测试

    @Test
    public void getUsers(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //底层主要应用反射:通过反射可以得到类的所有东西
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    
        List<User> users = mapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }
    
        sqlSession.close();
    }
    

本质:反射机制实现

底层:动态代理

8.3 mybatis详细的执行流程

image-20220822152647718

8.4 CRUD

CRUD是指在做计算处理时的增加(Create)、读取(Read)、更新(Update)和删除(Delete)几个单词的首字母简写。CRUD主要被用在描述软件系统中数据库或者持久层的基本操作功能。

我们可以在工具类创建的时候实现自动提交事务!

public static SqlSession getSqlSession(){
    //优化代码
    return sqlSessionFactory.openSession(true);  //设置自动提交事务为true
}

C:增加(Create)

//引用对象不需要加@Param(""):User   #{password}:要和实体类一致
@Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);
@Test
public void addUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //底层主要应用反射:通过反射可以得到类的所有东西
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    mapper.addUser(new User(6, "小苏", "123456"));

    sqlSession.close();
}

R:读取(Read)

@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);
@Test
public void getUserById(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //底层主要应用反射:通过反射可以得到类的所有东西
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user = mapper.getUserById(2);
    System.out.println(user);

    sqlSession.close();
}

U:更新(Update)

@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Test
public void updateUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //底层主要应用反射:通过反射可以得到类的所有东西
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    mapper.updateUser(new User(6, "小苏", "123123"));

    sqlSession.close();
}

D:删除(Delete)

@Delete("delete from user where id=#{id}")
int deleteUser(@Param("id") int id);
@Test
public void deleteUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //底层主要应用反射:通过反射可以得到类的所有东西
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    mapper.deleteUser(6);

    sqlSession.close();
}

注意:

我们必须要讲接口注册绑定到我们的核心配置文件中!

<!--绑定接口-->
<mappers>
    <mapper class="com.shu.dao.UserMapper"/>
</mappers>

关于@Param()注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家加上
  • 我们在SQL中引用的就是我们这里的@Param()中设定的属性名!

#{} ${} 区别

1)#{}是预编译处理,$ {}是字符串替换。
2)mybatis在处理两个字符时,处理的方式也是不同的:
①处理#{}时,会将sql中的#{}整体替换为占位符(即:?),调用PreparedStatement的set方法来赋值;
②在处理 $ { } 时,就是把 替换成变量的值。 3 )假如用 { } 替换成变量的值。 3)假如用 替换成变量的值。3)假如用{}来编写SQL会出现:恶意SQL注入,对于数据库的数据安全性就没办法保证了。

9、Lombok

Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能。不需要再写getter、setter或equals方法,只要有一个注解,你的类就有一个功能齐全的构建器、自动记录变量等等。

Lombok常用注解

▪ @Data

▪ @Getter

▪ @Setter

▪ @ToString

▪ @EqualsAndHashCode

▪ @NonNull

▪ @Log4j, @Log4j2, @Slf4j, @XSlf4j

▪ @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor

使用步骤:

  1. 在IDEA中安装lombok插件

    image-20220823135315355

  2. 在项目中导入lombok的jar包【maven仓库中去找到lombok的依赖】

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.24</version>
                <!--
                Lombok的作用域:去掉则可以运用于所有项目
                <scope>provided</scope>
                -->
            </dependency>
    
  3. 在实体类上使用Lombok注解

    image-20220823140243581

10、多对一的处理

多对一:

  • 多个学生,对应一个老师
  • 对于学生这边而言,关联 … 多个学生,关联一个老师 【多对一】
  • 对于老师而言,集合,一个老师有很多学生 【一对多】

SQL语句:

CREATE TABLE `teacher` (
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE = INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`,`name`) VALUES (1,'秦老师');

CREATE TABLE `student` (
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	`tid` INT(10) DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `fktid`(`tid`),
	CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE = INNODB DEFAULT CHARSET=utf8

INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('1','小明','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('2','小红','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('3','小张','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('4','小李','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('5','小王','1');

10.1测试环境搭建

  1. 导入lombok

  2. 新建实体类Teacher,Student

    @Data
    public class Teacher {
        private int id;
        private String name;
    }
    
    @Data
    public class Student {
        private int id;
        private String name;
    
        //一个学生需要关联一个老师,使用组合
        private Teacher teacher;
    }
    
  3. 建立Mapper接口

  4. 建立Mapper.xml文件

  5. 在核心配置文件中绑定注册我们的Mapper接口或者文件

  6. 测试查询是否能够成功

10.2 按照查询嵌套处理

public interface StudentMapper {
    //查询所有的学生信息,以及对应的老师的信息
    public List<Student> getStudent();

    public List<Student> getStudent2();
}
<!--
怎么将老师信息同学生信息一起查出来?
思路:
1.查询所有的学生信息
2.根据查询出来的学生的tid,寻找对应的老师   子查询!
-->

<select id="getStudent" resultMap="StudentTeacher">
    select * from student
</select>

<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--复杂的属性,我们需要单独处理
        对象:association
        集合:collection
    -->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>

</resultMap>

<select id="getTeacher" resultType="Teacher">
    select * from teacher where id=#{id}
</select>
@Test
public void testStudent(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> studentList = mapper.getStudent();
    for (Student student : studentList) {
        System.out.println(student);
    }
    sqlSession.close();
}

10.3 按照结果嵌套处理

<!--方法二:按照结果嵌套处理-->
<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="学生学号"/>
    <result property="name" column="学生姓名"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="教师姓名"/>
    </association>
</resultMap>

<select id="getStudent2" resultMap="StudentTeacher2">
    select
        s.id as `学生学号`,s.name as `学生姓名`,t.name as `教师姓名`
    from
        student s
            inner join
        teacher t
        on
            s.tid = t.id
</select>
@Test
public void testStudent2() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> studentList = mapper.getStudent2();
    for (Student student : studentList) {
        System.out.println(student);
    }
    sqlSession.close();
}

回顾Mysql多对一查询方式:

  • 子查询
  • 联表查询

11、一对多的处理

比如:一个老师拥有多个学生

对于老师而言,就是一对多的关系!

  1. 环境搭建,和刚才的一样

  2. 新建实体类Teacher、Student

    @Data
    public class Teacher {
        private int id;
        private String name;
        //一个老师拥有多个学生
        private List<Student> students;
    }
    
    @Data
    public class Student {
        private int id;
        private String name;
        private int tid;
    }
    
  3. 按照结果嵌套处理

    <!--按结果嵌套查询-->
    <resultMap id="TeacherStudent2" type="Teacher">
        <result property="id" column="教师编号"/>
        <result property="name" column="教师姓名"/>
        <!--
        javaType="":指定的属性的类型
        集合中的泛型信息,我们使用ofType获取  ofType="Student
        -->
        <!--取出来的每一个值,每一个值是对应的,不需要写javaType="ArrayList"-->
        <collection property="students" ofType="Student">
            <result property="id" column="学生编号"/>
            <result property="name" column="学生姓名"/>
            <!--<result property="tid" column="tid"/>-->
        </collection>
    </resultMap>
    
    <select id="getTeacher2" resultMap="TeacherStudent2">
        select t.id 教师编号,t.name 教师姓名,s.id 学生编号,s.name 学生姓名
        from teacher t,student s
        where t.id=s.tid and t.id=#{tid}
    </select>
    
  4. 按照查询嵌套处理

    <!--按照查询嵌套处理-->
    <select id="getTeacher3" resultMap="TeacherStudent3">
        select * from teacher where id=#{tid}
    </select>
    <resultMap id="TeacherStudent3" type="Teacher">
        <!--一样的字段可以省略不写-->
        <!--<result property="id" column="id"/>
        <result property="name" column="name"/>-->
        <!--
        下面这种本来就是一个集合,集合的对象需要加上javaType="ArrayList"
        select="getStudentByTeacherId":子查询语句
        -->
        <collection property="students" ofType="Student" javaType="ArrayList" select="getStudentByTeacherId" column="id"/>
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid=#{tid}
    </select>
    
  5. 测试查询

        @Test
        public void getTeacher2(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
            Teacher teacher2 = mapper.getTeacher2(1);
            System.out.println(teacher2);
            sqlSession.close();
        }
        @Test
        public void getTeacher3(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
            Teacher teacher2 = mapper.getTeacher3(1);
            System.out.println(teacher2);
            sqlSession.close();
        }
    }
    

小结

  • 关联:association【多对一】
  • 集合:collection 【一对多】
  • javaType & ofType的区别
    1. javaType 用来指定实体类中的属性的类型
    2. ofType 用来指定映射到List或者集合中的pojo类型,也就是泛型中的约束类型!

注意点

  • 保证SQL的可读性,尽量保证通俗易懂
  • 注意一对多和多对一中,属性名和字段的问题!
  • 如果问题不好排查错误,可以使用日志。建议使用Log4j

面试高频

  • MySQL引擎:INNODB和MYISAM
  • INNODB的底层原理
  • 索引
  • 索引优化!

12、动态SQL

什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

12.1 搭建环境

CREATE TABLE `blog`(
	`id` VARCHAR(50) NOT NULL COMMENT '博客id',
	`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
	`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
	`create_time` DATETIME NOT NULL COMMENT '创建时间',
	`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8

创建一个基础工程

  1. 导包

  2. 编写配置文件

  3. 编写实体类

    @Data
    public class Blog {
        private String id;
        private String title;
        private String author;
        //属性名和字段名不一致,需要在配置文件中开启驼峰命名自动映射
        /*
        <!--是否开启驼峰命名自动映射-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        */
        private Date createTime;
        private int views;
    }
    
  4. 编写实体类对应的Mapper接口和Mapper.xml文件

动态SQL之IF语句

//查询博客
List<Blog> queryBlogIF(Map map);
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</select>
@Test
public void queryBlogIf(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    map.put("title","Java");
    map.put("author","小狂");
    List<Blog> blogs = mapper.queryBlogIF(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

choose、when、otherwise

<select id="queryBlogChoose" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <choose>
            <when test="title !=null">
                title=#{title}
            </when>
            <when test="author !=null">
                and author=#{author}
            </when>
            <otherwise>
                and views=#{views}
            </otherwise>
        </choose>
    </where>
</select>

trim、where、set

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog
    <where>
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
    </where>
</select>
<update id="updateBlog" parameterType="map">
    /*set语句要加,*/
    update blog
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="author != null">
            author = #{author}
        </if>
    </set>
    where id=#{id}
</update>
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码

if

where , set , choose , when

12.2 foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  <where>
    <foreach item="item" index="index" collection="list"
        open="ID in (" separator="," close=")" nullable="true">
          #{item}
    </foreach>
  </where>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

//查询第1,2,3号记录的博客
List<Blog> queryBlogForeach(Map map);
<!--查询第1,2,3号记录的博客-->
<!--open="and (" close=")" separator="or":拼接SQL   and (  ) or-->
<!--select * from blog where 1=1 and (id=1 or id=2 or id=3)-->
<select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from blog
    <where>
    <foreach collection="ids" item="id" open="and (" close=")" separator="or">
        id=#{id}
    </foreach>
    </where>
</select>
@Test
public void queryBlogForeach(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    ArrayList<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);
    ids.add(3);
    map.put("ids",ids);

    List<Blog> blogs = mapper.queryBlogForeach(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
    sqlSession.close();
}

12.3 SQL片段

有的时候我们可能会将一些公共的部分抽取出来进行复用

image-20220827140550813

image-20220827140805127

<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>

总结:

  1. 使用sql标签抽取公共的部分
  2. 在需要的使用的地方使用include标签引用即可

注意事项:

  • 最好基于单表来定义SQL片段
  • 不要存在where标签

12.4 总结

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了!

建议:

  • 先在mysql中写出完整的sql,再对应的去修改我们的动态SQl实现通用即可

13、缓存【读写分离】

13.1 简介

查询:连接数据库,耗资源!
     一次查询的结果给,给它暂存在一个可以直接取到的地方!——》内存:缓存
我们再次查询相同的数据的时候,直接走缓存,就不用走数据库了!
  1. 什么是缓存[Cache]?
    存在内存中的临时数据。
    将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库查询文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

  2. 为什么使用缓存?
    减少和数据库的交互次数,减少系统开销,提高系统效率。

  3. 什么样的数据能使用缓存?

    经常查询并且不经常改变的数据。【可以使用缓存】

13.2 Mybatis缓存

Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

13.3 一级缓存

一级缓存也叫本地缓存:
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

测试步骤:

  1. 开启日志!

    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
  2. 测试在一个Session中查询两次相同记录

    //通过id获取一个用户
    User getUserById(@Param("id") int id);
    
    <select id="getUserById" resultType="user">
        select * from user where id=#{id}
    </select>
    
    @Test
    public void getUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
    
        System.out.println("======================");
        User user2 = mapper.getUserById(1);
        System.out.println(user2);
    
        System.out.println(user==user2);
        sqlSession.close();
    }
    
  3. 查看日志输出,走了一次SQL查出两条信息

    image-20220827160812387

缓存失效的情况:

  1. 查询不同的东西;

    @Test
    public void getUserById(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
    
        System.out.println("======================");
        User user2 = mapper.getUserById(2);
        System.out.println(user2);
    
        System.out.println(user==user2);
        sqlSession.close();
    }
    

    image-20220827161201731

  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

    image-20220827162732503

  3. 查询不同的Mapper.xml

  4. 手动清理缓存!

    image-20220827162829900

小结

  • 一级缓存默认是开启的,只在一次sqlSession中有效,也就是拿到连接到关闭连接这个区间!
  • 一级缓存就是一个Map!

13.4 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存;

  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;

    • 新的会话查询信息,就可以从二级缓存中获取内容;

    • 不同的mapper查出的数据就会放在自己对应的缓存(map)中;

步骤:

  1. 在mybatis-config.xml开启全局缓存

    <!--显示的开启全局缓存-->
    <setting name="cacheEnabled" value="true"/>
    
  2. 在要使用二级缓存的Mapper中开启

    <!--自定义参数-->   
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    
  3. 测试

    @Test
    public void cacheTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
    
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
        sqlSession.close();
    
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.getUserById(1);
        System.out.println(user2);
    
        System.out.println(user==user2);
    
        sqlSession2.close();
    }
    
    1. 问题:如果没有自定义参数,则会报错,我们需要将实体类序列化!

      <cache/>
      

      image-20220827170149875

    2. 设置好参数

      <cache
              eviction="FIFO"
              flushInterval="60000"
              size="512"
              readOnly="true"/>
      

      image-20220827165531349

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效;
  • 所有的数据都会先放在一级缓存中;
  • 只有当会话提交或者关闭的时候,才会提交到二级缓存中!

13.5 缓存原理

image-20220827172433497

13.6 自定义缓存-ehcache(可以了解)

Ehcache是一种广泛使用的开源Java分布式缓存,主要面向通用缓存。

要在程序中使用ehcache,先要导包!

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.2</version>
</dependency>

在mapper中指定使用我们的ehcache缓存实现!

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

配置ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统宕机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>

    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>

</ehcache>

目前:Redis数据库来做缓存!K-V

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值