Mybatis基础

Mybatis


官方文档链接

Mybatis

MyBatis是一个优秀的基于Java的持久层框架,支持自定义SQL,存储过程和高级映射。

MyBatis对原有JDBC操作进行了封装,几乎消除了所有JDBC代码,使开发者只需关注SQL本身。

MyBatis可以使用简单的XML或Annotation来配置执行sQL,并自动完成oRM操作,将执行结果返回。

ORM (Object Relational Mapping)对象关系映射,将程序中的一个对象与表中的一行数据一一对应。

ORM框架提供了持久化类与表的映射关系,在运行时参照映射文件的信息,把对象持久化到数据库中

JDBC的缺点

存在大量的冗余代码。
手工创建ConnectionStatement等。手工将结果集封装成实体对象。
查询效率低,没有对数据访问进行过优化(Not Cache)

环境搭建

整个项目的结构图
在这里插入图片描述

构建一个maven项目
在这里插入图片描述
在这里插入图片描述
在pom.xml中引入MyBatis核心依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mybatis01</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <properties>
        <java.version>13</java.version>
        <maven.compiler.source>${java.version}</maven.compiler.source>
        <maven.compiler.target>${java.version}</maven.compiler.target>
    </properties>
    
    <dependencies>
    
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

       
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>
        
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

</project>

由于我的jdk版本是13,所以多引入了一些东西不然会报错,如果不是这个版本可以不用引入,然后就是mysqljar包最好要与本地安装的mysql版本所匹配

创建Mybatis配置文件

在resources资源下创建mybatis.xml

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器

首先是官方文档的可以直接复制在做修改,没必要记忆

<?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>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

修改后如下

<?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>
<!--    JDBC环境配置,选中默认环境-->

    <environments default="MySqlDB">
<!--        MySql数据库环境配置-->

        <environment id="MySqlDB">
<!--            配置JDBC事务管理-->

            <transactionManager type="JDBC"/>
<!--     POOLED配置JDBC数据源连接池-->

            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/db3?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

<!--    Mapper注册-->

    <mappers>
<!--        注册Mapper文件的所在位置-->

        <mapper resource="UserDaoMapper.xml"/>

    </mappers>
</configuration>    

注意mapper.xml默认建议存放在resources中,路径不能以/开头

然后因为我的mysql的版本是8.0.20的所以连接时要加cj和时区等等有所不同,注意下自己的版本

mybatis开发步骤

建表

CREATE TABLE t_users(
    id int primary key auto_increment,
    name varchar(20),
    password varchar(20),
    sex varchar(2),
    register_time datetime


)charset =utf8;

定义实体类

定义所需的CURD操作的实体类
package com.blb.entity;

import java.util.Date;

public class User {
    private Integer id;
    private String name;
    private String password;
    private String sex;
    private Date registTime;

    public User() {
    }

    public User(String name, String password, String sex, Date registTime) {
        this.name = name;
        this.password = password;
        this.sex = sex;
        this.registTime = registTime;
    }

    public User(Integer id, String name, String password, String sex, Date registTime) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.sex = sex;
        this.registTime = registTime;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getRegistTime() {
        return registTime;
    }

    public void setRegistTime(Date registTime) {
        this.registTime = registTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", sex='" + sex + '\'' +
                ", registTime=" + registTime +
                '}';
    }
}

定义DAO接口

根据需要定义DAO接口,以及方法
package com.blb.dao;

import com.blb.entity.User;

public interface UserDao {
    public User selectUserById(Integer id);
}

编写UserDao.xml

在resources目录中创建UserMapper.xml文件
<?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接口-->        
<!--namespace = 所需实现的接口全限定名-->
<mapper namespace="com.blb.dao.UserDao">

<!--    id = 所需要重写的接口抽象方法名,resultType = 查询所需返回的对象类型-->
    <select id="selectUserById" resultType="com.blb.entity.User">
    

    select * from Blog where id = #{id}
    
  </select>
</mapper>

使用mybatis开发时我们不需要在写实现类,只需要写一个dao接口和xml的配置文件,在文件里面对应相应的dao

注意 Mapper.xml里面

namespace = 所需实现的接口全限定名

id = 所需要重写的接口抽象方法名,表示实现哪个方法的业务。

resultType = 查询所需返回的对象类型

parameterType = 是调⽤对应⽅法时参数的数据类型,如果简单类型或者基本类型可以不用显示给出,mybatis的类型处理器会自动识别。如果参数过多可以放入map中作为参数传递

如果方法有多个参数的时候,使用@Param注解取名。也可以通过arg0 ……argN   或者 param1 …… paramN来分别表示参数列表的第几个位置的参数。推荐使用@Param。

namespace 通常设置为⽂件所在包+⽂件名的形式。

resultMap:对外部 resultMap 的命名引用。 resultType 和 resultMap 之间只能同时使用一个。

insert 标签表示执⾏添加操作。
select 标签表示执⾏查询操作。
update 标签表示执⾏更新操作。
delete 标签表示执⾏删除操作。


增删改需要提交事务

注册Mapper

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。

Mapper.xml注册到mybatis.xml中

4种注册方式

1.<mapper resource="com/blb/dao/UserDao.xml"/>
设置Mapper.xml这种方式适用于根据statementId进行操作

2.<mapper class="com.blb.dao.UserDao"></mapper>
设置Mapper接口这种方式适用于接口绑定的方式

3.<mapper url=""></mapper>
使用磁盘口的绝对路径(基本不用)

4.<package name="com.blb.dao"/>
根据包设置mapper接口

接口和他的Mapper配置文件必须同名!
接口和他的Mapper配置文件必须在同一个包下!

注意接口的名字一定要与mapper.xml的名字一致否则如果用class和package方式注册mapper时会报错
如果在resources里面建立相同的包结构,需要一层一层的建立,否则一次建立的可能有问题

 <!--    Mapper注册-->
    <mappers>
        <!--        注册Mapper文件的所在位置-->
      <mapper resource="UserDao.xml"></mapper>

    </mappers>

测试

package com.blb.tests;

import com.blb.dao.UserDao;
import com.blb.entity.User;
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 org.junit.Test;

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

public class mybatisTest {
    @Test
    public void test01() {
        String resource = "mybatis.xml";
        InputStream inputStream = null;
        try {
//             读取mybatis配置文件的流对象
            inputStream = Resources.getResourceAsStream(resource);
            
//            构建SqlSession连接对象的工厂
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            
//            通过工厂创建连接对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            
//            通过连接对象获得接口的实现类
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            
//            调用接口的方法
            User user = mapper.selectUserById(1);
            System.out.println(user);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
}


注意点:

org.apache.ibatis.binding.BindingException: Type interface com.blb.userdao is not known to the MapperRegistry.

MapperRegistry是什么?

核心配置文件中注册 mappers

运行时可能出现的各种问题

我的jdk是13很新,idea的版本也是新的所以出现了各种bug这里我,困扰了很久一一列出来你可能也出现这个bug

首先是maven导入了依赖找不到包

在这里插入图片描述

IDEA install项目时报错Please refer to…for the individual test results.

两种解决方案

在maven 工程的pom.xml文件中加入

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<testFailureIgnore>true</testFailureIgnore> 
 
</configuration>
</plugin>
</plugins>
</build>

关闭maven的运行检查

在这里插入图片描述

一些细节

由于UserDao.xml和UserDao关系很密切所以最好是放在同一级目录下,但是又由于maven默认资源放在resources目录下,放在java下的xml文件编译时会被忽略

解决mapper.xml存放在resources以外路径中的读取问题
修改maven默认规则

在pom.xml文件最后追加< build >标签,以便可以将xml文件复制到classes中,并在程序运行时正确读取。
<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include><!-- 新添加 */代表1级目录 **/代表多级目录 -->
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include><!-- 新添加 */代表1级目录 **/代表多级目录 -->
                <include>**/*.properties</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

注意修改mybatis.xml里的Mapper注册

<!--    Mapper注册-->

    <mappers>
        <!--        注册Mapper文件的所在位置-->

        <mapper resource="com/blb/dao/UserDao.xml"/>
        
        <mapper class="com.blb.dao.UserDao"></mapper>
		<package name="com.blb.dao"/>
    </mappers>

注意这里resource应该以/分隔我一开始写成了.又是卡了许久,如果是以class或者package方式配置的还是写.

环境配置

MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。例如,开发、测试和生产环境需要有不同的配置。**尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。**为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可
可以接受环境配置的两个方法签名是:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);

如果忽略了环境参数,那么将会加载默认环境,如下所示:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);

environments 元素下可以定义多个environment元素,每个environment都可以id命名,而environments 通过default属性值来定默认操作environment的环境。

  • MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

    设置成JDBC表示使用数据源的事务管理,设置成MANAGED使用容器的事务管理。如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置

  • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。dataSource元素type="[UNPOOLED|POOLED|JNDI]"。分别表示不使用池/使用池/服务器的JNDI服务来获取数据的连接。

properties配置文件

对于mybatis.xml的核心配置中,如果存在需要频繁改动的内容,可以提取到properties中

jdbc.properties

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

注意原来写的&amp;在xml表示&现在可以全部换回&了

修改mybatis.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>
<!--    添加properties配置文件路径(外部配置,动态替换)-->

    <properties resource="jdbc.properties"></properties>
    <!--    JDBC环境配置,选中默认环境-->

    <environments default="MySqlDB">
        <!--        MySql数据库环境配置-->

        <environment id="MySqlDB">
            <!--            配置JDBC事务管理-->

            <transactionManager type="JDBC"/>
            <!--     POOLED配置JDBC数据源连接池-->

            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--    Mapper注册-->

    <mappers>
        <!--        注册Mapper文件的所在位置-->

        <mapper resource="com/blb/dao/UserDaoMapper.xml"/>

    </mappers>
</configuration>
  <!--引入外部配置文件-->
    <properties resource="db.properties">
        <property name="username" value="root"/>
        <property name="pwd" value="11111"/>
    </properties>

可以直接引入外部文件
可以在其中增加一些属性配置

还可以在创建SqlSessionFactory对象的时候设置属性:

Properties p = new Properties();
p.put("driver", "com.mysql.jdbc.Driver");
p.put("url", "jdbc:mysql://localhost:3306/mybatis-db?useUnicode=true&characterEncoding=UTF-8");
p.put("username", "root");
p.put("password", "root");
		
SqlSessionFactory sqlSessionFactory = new qlSessionFactoryBuilder().build(inputStream,p);

如果外部文件和标签里配置的属性有同一个字段,优先使用外部配置文件的
java中的设置方式优先级>外部properties文件>xml配置文件中的定义

配置文件内的标签的顺序

configuration (properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, reflectorFactory?, plugins?, environments?, databaseIdProvider?, mappers?)>

注意配置文件内部的顺序一定要以这规定的为准,否则会报错

类型别名

为实体类定义别名,提高书写效率
型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。主要通过配置文
件中的typeAliases标签来定义
<mapper namespace="com.blb.dao.UserDao">

    <!--    id = 所需要重写的接口抽象方法名,resultType = 查询所需返回的对象类型-->
    <select id="selectUserById" resultType="com.blb.entity.User">


    select * from t_users where id = #{id}

  </select>

resultType="com.blb.entity.User"里面实体类太长了所以我们要简化

在mybatis.xml里面修改如下
有两种方式

1. 直接定义类型别名

<!--    实体类别名-->
    <typeAliases>
    <!-- 给com.blb.entity.User类型取个别名user_dyk,在映射文件中可以直接使用别名 -->
        <typeAlias type="com.blb.entity.User" alias="user_dyk"></typeAlias>
    </typeAliases>

2.自动扫描包,将原类名作为作为别名(忽略大小写)

最好使用第二种

<typeAliases>
     	<!-- 把com.blb.entity包下的所有bean取名别   -->
         <package name="com.blb.entity"/>
</typeAliases>

直接扫描某个包,给所有bean类型自动取别名,规则为类名的首字符小写,但是好像忽略了大小写

注意第二种和第一种写的路径不一样,第二种不要把完整路径写上,写到实体类的上一级,默认别名就是类名,例如com.blb.entity.User它的别名就是User

3.使用@Alias("") 注解

还可以为包里面的类单独设置个性别名,如果这种默认取名规则我们不需要,可以通过注解来DIY:

@Alias("users")

注意使用了注解后默认的以类为别名的名字作为别名会失效

<?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>
<!--    添加properties配置文件路径(外部配置,动态替换)-->

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

<!--    实体类别名-->
<!--    <typeAliases>-->
<!--        <typeAlias type="com.blb.entity.User" alias="user_dyk"></typeAlias>-->
<!--    </typeAliases>-->

     <typeAliases>
         <package name="com.blb.entity"/>
     </typeAliases>
    <!--    JDBC环境配置,选中默认环境-->

    <environments default="MySqlDB">
        <!--        MySql数据库环境配置-->

        <environment id="MySqlDB">
            <!--            配置JDBC事务管理-->

            <transactionManager type="JDBC"/>
            <!--     POOLED配置JDBC数据源连接池-->

            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--    Mapper注册-->

    <mappers>
        <!--        注册Mapper文件的所在位置-->

        <mapper resource="com/blb/dao/UserDaoMapper.xml"/>

    </mappers>
</configuration>

Mybatis还内置了许多常见类型的别名

Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格
在这里插入图片描述
规律就是:
如果是基本类型那么就是在前面加一个_
如果是包装类型就是转化为小写

设置(settings)

  <!-- 配置数据库字段名跟属性名小驼峰匹配 -->
   <setting name="mapUnderscoreToCamelCase" value="true"/>
   
  <!-- 设置日志的实现方式 -->
   <setting name="logImpl" value="STDOUT_LOGGING"/>
   
  <!--  二级全局开关是默认开启的,但为了有好的可读性最好也写上 -->
  <setting name="cacheEnabled" value="true"/> 
  
  <!--  获取参数映射的时候可以通过#{0} #{1}..来映射,不推荐 -->
  <setting name="useActualParamName" value="false" />
  
   <!--  打开全局关联查询懒加载的开关 -->
   <setting name="lazyLoadingEnabled" value="true"/>

生命周期和作用域

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

SqlSessionFactoryBuilder:

  • 一旦创建了 SqlSessionFactory,就不再需要它了
  • 局部变量

SqlSessionFactory:

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

SqlSession

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

日志配置

什么是日志

日志文件是记录系统操作事件的记录文件,日志保存历史数据,是诊断问题及理解系统活动的重要依据

STDOUT_LOGGING

在核心配置文件中增加如下设置选项则可以打开日志选项

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

tip: 注意大小写要完全一样,不能有多的空格。

log4j

Log4j是apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX、Syslog、守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

  • 引入依赖
  <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
  </dependency>
在核心配置文件中增加如下设置选项则可以打开日志选项:
<setting name="logImpl" value="LOG4J"/>
在资源目录新建log4j的配置文件(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=./log/seven.log
  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=error
  log4j.logger.java.sql=DEBUG
  log4j.logger.java.sql.Statement=DEBUG
  log4j.logger.java.sql.ResultSet=DEBUG
  log4j.logger.java.sql.PreparedStatement=DEBUG
手动使用log4j
Logger logger = Logger.getLogger(StudentMapperTest.class);
logger.debug("log4j: debug ... ");
logger.info("log4j: info ... ");
logger.warn("log4j: warn ... ");
logger.error("log4j: error ... ");

logback

Logback是由log4j创始人设计的又一个开源日志组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。logback-core是其它两个模块的基础模块。logback-classic是log4j的一个 改良版本。此外logback-classic完整实现SLF4J API使你可以很方便地更换成其它日志系统如log4j或JDK14 Logging。logback-access访问模块与Servlet容器集成提供通过Http来访问日志的功能

在pom.xml添加依赖

<dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.30</version>
</dependency>
 
<dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.2.3</version>
</dependency>    

在resources下创建logback.xml

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <!-- 添加输出器(追加器) 作用:日志在什么地方进行输出
     ch.qos.logback.core.ConsoleAppender:向控制台进行打印输出
     -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoder:编码-->
        <encoder>
            <!-- pattern:规定日志输出的格式
                %d{HH:mm:ss.SSS}:开头的时间
                [%thread]:输出线程的名称
                %-5level:日志的级别 -5表示按5个字符右对齐
                %logger{36}:表示那个类进行日志输出 36:这段字符串最多允许36个字符串,超过则会用简写的方式输出
                -%msg:具体日志输出的内容
                %n:换行
            -->
            <pattern>[%thread] %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <!-- 日志打印的根标签 level:日志输出级别-->
    <root level="debug">
        <!-- 引用appender name="console" 在日志输出的过程中会,只要是等于debug级别或以上都会按照 appender name="console"的格式进行打印-->
        <appender-ref ref="console"/>
    </root>
</configuration>

level日志等级

日志输出级别(优先级高到低)
error: 错误 - 系统故障日志
warn: 警告 - 存在风险
info: 一般性消息
debug: 程序内部用于调试
trace: 程序运行的跟踪信息

trace<debug<info<warn<error

Mybatis的CRUD操作

元素描述备注
select查询语句可以自定义参数,返回集
insert插入语句执行和返回一个整数,代表插入的行数
update更新语句执行后返回一个整数,代表更新的行数
delete删除语句执行和返回一个整数,代表删除的行数
sql定义一部分sql,在多个位置被引用例如一张表列名一次定义,可以在多个sql语句中使用
resultMap用来描述从数据库结果集中来加载的对象提供映射规则

获取参数

#{}

select * from stu where id=?
类似于jdbc里面的preparedstatement,用?来占位

会经过jdbc的preparedstatement的预编译,会根据不同的数据类型来编译成对应的数据库所对应的数据
能够有效防止sql注入

${}

类似于 select * from stu where id="+id
字符串拼接

一般用#{}传参

参数传递处理:

单个参数:

mybatis不会做任何特殊要求
#{任何字符获取参数}

多个参数

mybatis会进行封装
会将传进来的值封装成为map

javaBean参数

单个参数 Emp selectEmp(Emp emp)
获取方式直接使用属性名
  emp.id====>#{id}
  emp.username ====#{username}

多个参数Emp selectEmp(Integer num,Emp emp)
  num===>#{param1}或者@Param
  emp===>必须加上对象别名:emp.id===>#{param2.id}
  或者 @param("emp") Emp emp ===>#{emp.id}
  emp.username===>#{param2.username} 
  或者@ Param("emp") Emp emp====>#{emp.username}

集合或者数组

如果是list mybatis会自动封装为map
{key:"list" value:username}
没用@param(" ")
           要获得username.get(0) ====> #{list[0]}
                      username.get(0)====>  #{arg0[0]}@param("usernames")
     要获得username.get(0)====>  #{usernames[0]}
                      username.get(0)====>  #{param1[0]}
 如果是数组,MyBatis会自动封装为map:
      {key:"array" value:usernames}  
      没用@Param("")要获得:
             username.get(0)=====>  #{array[0]}
             username.get(0)=====> #{arg0[0]}@param("usernames")
     要获得username.get(0)====>  #{usernames[0]}
                      username.get(0)====>  #{param1[0]}
             
                                         

map参数

和javaBean的参数传递是一样
一般情况:
请求进来的参数和pojo对应,就用pojo
请求进来的参数,没有和pojo对应的,就用map

查询

标签 <select id="" resultType="">

- id : 就是对应的namespace中的方法名;
- resultType:Sql语句执行的返回值!
- parameterType : 参数类型!

一个参数

<!--  #{}表示值,跟方法中的形参列表中的名称一致 ,parameterType参数类型,resultType结果类型-->
<select id="selectUserById" resultType="User">


    select * from t_users where id = #{id}

  </select>

一个参数时,#{}里面写任意名称都可以取值,但是尽量还是取和参数一样的名字

多个参数

1.
</select>

    <select id="selectUserByIdAndUsername" resultType="User">


    select * from t_users where id = #{arg0} and name= #{arg1}

  </select>

2.

 <select id="selectUserByIdAndUsername" resultType="User">


    select * from t_users where id = #{param1} and name= #{param2}

  </select>

注意arg和param的区别,arg是从0开始的param是从1开始的

注解参数绑定

import org.apache.ibatis.annotations.Param; //引入注解

//使用mybatis提供的@param进行参数绑定
public User selectUserByIdAndPassword(@Param("id") Integer id,@Param("password") String password);
}
 <select id="selectUserByIdAndPassword" resultType="User">


    select * from t_users where id = #{id} and password= #{password}

  </select>

map参数绑定

//添加map进行参数绑定
    public User selectUserByIdAndPassword_map(Map values);
			Map mp=new HashMap();
            mp.put("id",1);
            mp.put("password","123");
            User user =mapper.selectUserByIdAndPassword_map(mp);
            System.out.println(user);
<select id="selectUserByIdAndPassword_map" resultType="User">


    select * from t_users where id = #{id} and password= #{password} //通过key值获取

  </select>

#{}里面填的是存入的键值即map的key值

对象参数绑定

//使用对象属性进行参数绑定
public User selectUserByIdAndPassword_object(User user);
 User user=new User();
            user.setId(1);
            user.setPassword("123");
            User user1=mapper.selectUserByIdAndPassword_object(user);
            System.out.println(user1);
<select id="selectUserByIdAndPassword_object" resultType="User">


    select * from t_users where id = #{id} and password= #{password}

  </select>

#{}里面填的是User对象的属性值

模糊查询

public List<User> selectByKeywords(String keywords);
List<User> list = mapper.selectByKeywords("y");
             for(User u:list){
                 System.out.println(u);
             }
<select id="selectByKeywords" resultType="User">


    select * from t_users where name like concat('%',#{keywords},'%')

  </select>

注意当返回值是集合时resultType的值是集合泛型的类型
然后就是模糊查询可以用concat来拼接

删除

标签: <delete id="" parameterType="">

<delete id="deleteUser" parameterType="int">

    delete from t_users where id=#{id}
  </delete>
public int deleteUser(@Param("id") Integer id);
int i = mapper.deleteUser(2);
System.out.println(i);
sqlSession.commit();

注意增删改和查不一样,一定要注意事务的提交,否则不会影响数据库

修改

标签: <update id="" parameterType="">
public int updateUser(User user);
<update id="updateUser" parameterType="user">
    update t_users set name=#{name},password=#{password},sex=#{sex},registTime=#{registTime} where id=#{id}
 //当方法参数为对象时,可以直接使用#{属性名}进行获取   
  </update>

添加

标签: <insert id="" parameterType="">
 public int insertUser(User user);
User u=new User(100,"cb","123","M",new Date());
            int i = mapper.insertUser(u);
            System.out.println(i);
            sqlSession.commit();

自动增长主键

<insert id="insertUser" parameterType="user">
    insert into t_users(id,name,password,sex,registTime) values (NULL,#{name},#{password},#{sex},#{registTime})
  </insert>


<insert id="insertUser" parameterType="user">
    insert into t_users(name,password,sex,registTime) values (#{name},#{password},#{sex},#{registTime})
  </insert>

手动增长

<insert id="insertUser" parameterType="user">
    insert into t_users(id,name,password,sex,registTime) values (#{id} ,#{name},#{password},#{sex},#{registTime})
  </insert>

主键回填

在 MySQL 中主键自增字段,在插入后往往需要获得这个主键,以便于后面的操作,而 MyBatis 提供了实现的方法
主键回填,将新数据的id,存入java对象的主键和属性中

标签 <selectKey id="" paramterType="" order="AFTER|BEFORE">

两种方式

1.通过last_insert_id()查询主键

适用于主键是整数自增类型

public int insertUser(User user);
<insert id="insertUser" parameterType="user">

     <selectKey keyProperty="id" resultType="int" order="AFTER">   <!--插入之后 -->
       select last_insert_id() //返回最后一条数据的id
     </selectKey>
    insert into t_users(id,name,password,sex,registTime) values (#{id} ,#{name},#{password},#{sex},#{registTime})
  </insert>

AFTER代表插入之后再执行,因为是自动增长的,插入之后在回填
要通过设置 keyProperty 来指定将查询到的数据绑定到哪个属性上
主键回填字段keyProperty的参数实际上是public int insertUser(User user);这个函数的参数user对象的id属性,

keyProperty 将当前的查询结果放到那个实体类属性中
resultType 返回的数据类型
order 设置在增删改之前还是之后运行

这种方式还可以通过直接配置查询中的语句来实现

 <insert id="insertUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">


    insert into t_users(id,name,password,sex,regist_time) values (#{id} ,#{name},#{password},#{sex},#{registTime})
  </insert>
useGeneratedKeys 获取插入后自动增长的主键
keyProperty 将自动增长的主键赋值到那个属性中
获取自动增长的注解: useGeneratedKeys="true" keyProperty="id
2.通过uuid()查询主键

适用于字符类型的主键,没有自动增长

<insert id="insertUser" parameterType="user">

     <selectKey keyProperty="id" resultType="string" order="BEFORE">   <!--插入之前 -->
       select repalce(uuid(),'-','');
     </selectKey>
    insert into t_users(id,name,password,sex,registTime) values (#{id} ,#{name},#{password},#{sex},#{registTime})
  </insert>

先查询uuid在回填,因为先插入的话会因为主键不能为空而报错,所有
mysql内置函数replace()和uuid生成一个主键,然后回填到id

增删改的返回值,除了可以声明int还可以声明boolean(如果大于1就会返回true)

MyBatis工具类

封装工具类

Resource:用于获得读取配置文件的io对象,耗费资源,建议通过id一次性读取所需要的数据

sqlSessionFactory:SqlSession工厂类,内存占用多,耗费资源,建议每个应用只创建一个对象

SqlSession:相当于Connection,可以控制事务,应为线程私有,不被多线程共享

将获得的连接,关闭连接,提交事务,回滚事务,获得接口实现类等方法进行封装
package com.blb.utils;

import com.mysql.cj.Session;
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;

public class MyBatisUtils {

    //获得sqlSession工厂
    private static SqlSessionFactory factory;

    //创建threadLocal绑定当前线程SQLSession对象
    private static  final  ThreadLocal<SqlSession> THREAD_LOCAL=new ThreadLocal<SqlSession>();

    static {

        String resource = "mybatis.xml";
        InputStream inputStream = null;
        try {

           // 读取mybatis配置文件的流对象
            inputStream = Resources.getResourceAsStream(resource);
            factory= new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //获得连接从THREAD_LOCAL中获取当前线程的sqlsession

    public  static SqlSession openSession()
    {
        SqlSession sqlSession=THREAD_LOCAL.get();
        if (sqlSession==null)
        {
            sqlSession= factory.openSession();
            THREAD_LOCAL.set(sqlSession);
        }
        return sqlSession;
    }

    //释放连接 释放当前线程中的SqlSession
    public  static void closeSession()
    {
        SqlSession session=THREAD_LOCAL.get();
        session.close();
        THREAD_LOCAL.remove();
    }

    //提交事务(提交当前线程中的sqlSession)所管理的事务
    public static void commit()
    {
        SqlSession session=openSession();
        session.commit();
        closeSession();
    }

    //回滚事务(回滚当前线程中的sqlSession)所管理的事务)
    public static void  rollback()
    {

        SqlSession session=openSession();
        session.rollback();
        closeSession();
    }

    //获得接口实现类对象
    public static <T> T getMapper(Class<T> mapper)
    {
         SqlSession session=openSession();
         return session.getMapper(mapper);


    }

}

User u=new User(null,"lfw123","123","M",new Date());

        try {
            UserDao mapper = MyBatisUtils.getMapper(UserDao.class);

            mapper.insertUser(u);
            MyBatisUtils.commit();
        }catch (Exception e)
        {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }

ORM映射

Mybatis自动ORM失效

Mybatis只能自动维护库表列名与属性名相同的一一对应关系,二者不同时,无法自动ORM

方法一列的别名

在SQL中使用as为查询字段添加列别名,以匹配属性名
<select id="selectUserById" resultType="User">

select id,name,password,sex,regist_time as registTime from t_users where id = #{id}

方法二 结果映射(ResultMap 查询结果的封装规则)

resultMap 元素是 MyBatis 中最重要最强大的元素。主要用来把数据库中的字段跟java中的属性按照我们的意图来映射。当我们数据库字段跟属性不一样时我们可以把这种映射关系在resultMap中来手动一一映射


        resultMap 用来自定义结果集和实体类的映射
            属性:
                id 相当于这个resultMap的唯一标识
                type 用来指定映射到哪个实体类
        id标签  用来指定主键列的映射规则
            属性:
                property 要映射的属性名
                column  对应的列名
        result标签 用来指定普通列的映射规则
            属性:
                property 要映射的属性名
                column 对应的列名
  
通过<resultMap id="" type="" >映射,匹配列名与属性名

<!--column数据库中的字段,property实体类中的属性-->

<id property="" column="" > 关联主键与列名
<result property="" column=""> 关联属性与列名

<select id="" resultMap="">resultMap与上面里面的id一致
     <!--定义resultMap标签 -->
     <resultMap id="user_resultMap" type="User">
     
     <!-- 关联主键与列名-->
       <id property="id" column="id"></id>
       
     <!--关联属性与列名 -->
      <!--column数据库中的字段,property实体类中的属性-->
       <result property="name" column="name"></result>
       <result property="password" column="password"></result>
       <result property="sex" column="sex"></result>
       <result property="registTime" column="regist_time"></result>


     </resultMap>
    <!--使用resultMap作为ORM映射依据 -->
    <select id="selectUserById" resultMap="user_resultMap">


    select id,name,password,sex,regist_time from t_users where id = #{id}

  </select>

Mybatis处理关联关系,多表连接

实体间的关系:关联关系(拥有has,属于belong)
onetoone一对一关系
onetomany一对多关系
manytomany多对多关系

关系属性:将关系的另一方,作为本方法属性进行保存

关系方向:.只能从关系的一方,查找到关系的另一方,则成为单向关系
二.在关系的任何一方,都可查到关系的另一方,则成为双向关系

级联关系
当访问关系的一方时,如果需要查看与之关联的另一方数据,则必须使用表连接查询,将查询的另一方数据,保存在本方的属性中

建表

create table t_passangers(
                             id int primary key  auto_increment,
                             name varchar(50),
                             sex varchar(1),
                             birthday date


)default charset=utf8;

create table t_passports(
    id int primary key  auto_increment,
    nationality varchar(50),
    expire date,
    passenger_id int unique,
    foreign key (passenger_id) references t_passangers(id)


)default  charset =utf8;

insert  into t_passangers values (null,'dyk','m','2021-03-28');
insert  into t_passangers values (null,'cb','f','2021-03-28');

insert into  t_passports values (null,'china','2021-04-11',1);
insert into  t_passports values (null,'usa','2021-05-11',2);

首先建立了一张护照表和一张乘客表,一个乘客有一个护照,一个护照也只能属于一个乘客

首先是实体类
Passenger 实体类

package com.blb.entity;

import java.util.Date;

public class Passenger {
    private Integer id;
    private String name;
    private String sex;
    private Date birthday;


    //在存储旅客的护照信息: 关系属性
    private Passport passport;
    public Passenger() {
    }

    public Passenger(String name, String sex, Date birthday) {
        this.name = name;
        this.sex = sex;
        this.birthday = birthday;
    }

    public Passenger(Integer id, String name, String sex, Date birthday) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.birthday = birthday;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Passenger{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", birthday=" + birthday +
                ", passport=" + passport +
                '}';
    }
}

Passport 类

package com.blb.entity;

import java.util.Date;

public class Passport {
    private Integer id;
    private String nationality;
    private Date expire;

    //存储旅客信息: 关系属性
    private Passenger passenger;
    public Passport() {
    }

    public Passport(String nationality, Date expire) {
        this.nationality = nationality;
        this.expire = expire;
    }

    public Passport(Integer id, String nationality, Date expire) {
        this.id = id;
        this.nationality = nationality;
        this.expire = expire;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }

    public Date getExpire() {
        return expire;
    }

    public void setExpire(Date expire) {
        this.expire = expire;
    }

    @Override
    public String toString() {
        return "Passport{" +
                "id=" + id +
                ", nationality='" + nationality + '\'' +
                ", expire=" + expire +
                '}';
    }
}

可以看到两个实体类都添加了关系属性,数据库中是不存在这些列的

onetoone

package com.blb.dao;

import com.blb.entity.Passenger;
import org.apache.ibatis.annotations.Param;

public interface PassengerDaoMapper {
    //通过旅客的id,查询旅客信息及其护照信息 关联查询  联机查询
    Passenger queryPassangerById(@Param("id") Integer id);
}

<?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 = 所需实现的接口全限定名-->
<mapper namespace="com.blb.dao.PassengerDaoMapper">

<!--     结果映射(查询结果的封装规则)-->
    <resultMap id="passanger_passport" type="Passenger">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="sex" property="sex"></result>
        <result column="birthday" property="birthday"></result>

<!--         关系表中数据的封装规则指定关系表中的实体类型-->

<!--         将查询到的另外3个id,nationality,expire封装到Passenger的passport属性-->
        <association property="passport" javaType="Passport">
            <id column="id" property="id"></id>
            <result column="nationality" property="nationality"></result>
            <result column="expire" property="expire"></result>
        </association>
    </resultMap>
    
<select id="queryPassangerById" resultMap="passanger_passport">
    select t_passangers.id,name,sex,birthday,t_passports.id,nationality,expire from t_passangers inner join t_passports  on t_passangers.id = t_passports.id where t_passangers.id=#{id};
</select>
</mapper>
try {
           PassengerDaoMapper mapper = MyBatisUtils.getMapper(PassengerDaoMapper.class);

           Passenger passenger = mapper.queryPassangerById(1);
            System.out.println(passenger);
        }catch (Exception e)
        {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }
注意指定一方关系时(对象)使用<association javaType=" " property="" >

onetomany

create table t_department(
    id int primary key  auto_increment,
    name varchar(50),
    location varchar(100)

)default charset =utf8;

create table t_employee(
    id int primary key auto_increment,
    name varchar(50),
    salary double,
    dept_id int,
    foreign key (dept_id) references t_department(id)
)default charset =utf8;

insert into t_department values(1,"教学部","北京");
insert into t_department values(2,"研发部","武汉");

insert into t_employee values(1,"dyk",8000,1),
                                (2,"cb",7000,2),
                                (3,'lfw',6000,1);

一个部门可以有多个员工,一个员工属于一个部门

package com.blb.dao;

import com.blb.entity.Department;
import org.apache.ibatis.annotations.Param;

public interface DepartmentDao {
    //查询部门,及所有员工信息
    Department queryDepartmentById(@Param("id") Integer id);

}
<?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 = 所需实现的接口全限定名-->
<mapper namespace="com.blb.dao.DepartmentDao">

    <resultMap id="department_employee" type="Department">

        <id property="id" column="id"></id>
        <result column="name" property="name"></result>
        <result column="location" property="location"></result>

       <collection property="employees" ofType="Employee">
           <id property="id" column="id"></id>
           <result column="name" property="name"></result>
           <result column="salary" property="salary"></result>

       </collection>
    </resultMap>

    <select id="queryDepartmentById" resultMap="department_employee">

     select t_department.id,t_department.name,t_department.location,t_employee.id,t_employee.name,t_employee.salary from t_department inner join t_employee  on t_department.id = dept_id where t_department.id=#{id}
    </select>
</mapper>
try {
           DepartmentDao mapper = MyBatisUtils.getMapper(DepartmentDao.class);


             Department department = mapper.queryDepartmentById(1);
            System.out.println(department);
            List<Employee> employees = department.getEmployees();
            for(Employee e:employees){
                System.out.println(e);
            }
        }catch (Exception e)
        {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }

注意:指定多方关系时(集合)使用

manytomany

create table t_students(
    id int primary key  auto_increment,
    name varchar(50),
    sex varchar(1)


)default charset =utf8;

create table t_subjects(
    id int primary key auto_increment,
    name varchar(50),
    grade int
)default charset =utf8;

create table t_stu_sub(
    student_id int,
    subject_id int,
    foreign key (student_id) references t_students(id),
    foreign key (subject_id) references t_subjects(id),
    primary key (student_id,subject_id)

)default charset =utf8;


select t_subjects.id,t_subjects.name,t_subjects.grade,t_students.id,t_students.name,t_students.sex
from t_subjects inner join t_stu_sub  on t_subjects.id = t_stu_sub.subject_id
inner join t_students  on t_stu_sub.student_id = t_students.id
where t_subjects.id=1002

一个学生表一个课程表,一个学生可以对应多个课程,一个课程可以对应多个学生,所以需要第三张表来连接两个表

package com.blb.dao;

import com.blb.entity.Subject;
import org.apache.ibatis.annotations.Param;

public interface SubjectDao {
    public Subject querySubjectById(@Param("id") Integer id);
}

<?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 = 所需实现的接口全限定名-->
<mapper namespace="com.blb.dao.SubjectDao">
    <resultMap id="subject_student" type="subject">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="grade" property="grade"></result>

        <collection property="students" ofType="student">
            <id column="id" property="id"></id>
            <result column="name" property="name"></result>
            <result column="sex" property="sex"></result>
        </collection>

    </resultMap>
   <select id="querySubjectById" resultMap="subject_student">

       select t_subjects.id,t_subjects.name,t_subjects.grade,t_students.id,t_students.name,t_students.sex
from t_subjects inner join t_stu_sub  on t_subjects.id = t_stu_sub.subject_id
inner join t_students  on t_stu_sub.student_id = t_students.id
where t_subjects.id=#{id}
   </select>
</mapper>

更多细节

案例sql

CREATE DATABASE /*!32312 IF NOT EXISTS*/`mybatis_db` /*!40100 DEFAULT CHARACTER SET utf8 */;

USE `mybatis_db`;
DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `createtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',
  `price` int(11) DEFAULT NULL COMMENT '价格',
  `remark` varchar(100) DEFAULT NULL COMMENT '备注',
  `user_id` int(11) DEFAULT NULL COMMENT '用户id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
insert  into `orders`(`id`,`createtime`,`price`,`remark`,`user_id`) values (1,'2014-06-26 16:55:43',2000,'无',2),(2,'2021-02-23 16:55:57',3000,'无',3),(3,'2021-02-23 16:56:21',4000,'无',2);
DROP TABLE IF EXISTS `role`;

CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL COMMENT '角色名',
  `desc` varchar(100) DEFAULT NULL COMMENT '角色描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

/*Data for the table `role` */

insert  into `role`(`id`,`name`,`desc`) values (1,'总经理','一人之下'),(2,'CFO',NULL);

/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;

/*Data for the table `user` */

insert  into `user`(`id`,`username`,`age`,`address`) values (2,'pdd',26,NULL),(3,'UZI',19,'上海11'),(4,'RF',19,NULL);

/*Table structure for table `user_role` */

DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (
  `user_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `user_role` */

insert  into `user_role`(`user_id`,`role_id`) values (2,2),(2,1),(3,1);

基本使用

   <!--
        resultMap 用来自定义结果集和实体类的映射
            属性:
                id 相当于这个resultMap的唯一标识
                type 用来指定映射到哪个实体类
        id标签  用来指定主键列的映射规则
            属性:
                property 要映射的属性名
                column  对应的列名
        result标签 用来指定普通列的映射规则
            属性:
                property 要映射的属性名
                column 对应的列名
    -->
    <resultMap id="orderMap" type="com.sangeng.pojo.Order" >
        <id column="id" property="id"></id>
        <result column="createtime" property="createtime"></result>
        <result column="price" property="price"></result>
        <result column="remark" property="remark"></result>
        <result column="user_id" property="userId"></result>
    </resultMap>

	<!--使用我们自定义的映射规则-->
    <select id="findAll" resultMap="orderMap">
        SELECT id,createtime,price,remark,user_id  FROM ORDERS
    </select>

自动映射

我们定义resultMap时默认情况下自动映射是开启状态的。也就是如果结果集的列名和我们的属性名相同是会自动映射的我们只需要写特殊情况的映射关系即可

面这种写法和上面的写法会有相同的效果,因为其他属性的属性名和结果集的列名都是相同的会自动映射

    <resultMap id="orderMap" type="com.sangeng.pojo.Order" >
        <result column="user_id" property="userId"></result>
    </resultMap>
	<!--使用我们自定义的映射规则-->
    <select id="findAll" resultMap="orderMap">
        SELECT id,createtime,price,remark,user_id  FROM ORDERS
    </select>

如有需要可以选择关闭自动映射可以把resultMap的autoMapping属性设置为false

    <resultMap id="orderMap" type="com.sangeng.pojo.Order" autoMapping="false">
        <id column="id" property="id"></id>
        <result column="createtime" property="createtime"></result>
        <result column="price" property="price"></result>
        <result column="remark" property="remark"></result>
        <result column="user_id" property="userId"></result>
    </resultMap>

继承映射关系

我们可以使用resultMap 的extends属性来指定一个resultMap,从而复用重复的映射关系配置。

  	<!--定义个父映射,供其他resultMap继承-->
	<resultMap id="baseOrderMap" type="com.sangeng.pojo.Order" >
        <id column="id" property="id"></id>
        <result column="createtime" property="createtime"></result>
        <result column="price" property="price"></result>
        <result column="remark" property="remark"></result>
    </resultMap>
	<!--继承baseOrderMap,然后只需要写自己特有的映射关系即可-->
    <resultMap id="orderMap" type="com.sangeng.pojo.Order" autoMapping="false" extends="baseOrderMap">
        <result column="user_id" property="userId"></result>
    </resultMap>

一对一关系

两个实体之间是一对一的关系。(例如我们需要查询订单,要求还需要下单用户的数据。这里的订单相对于用户是一对一。)

方式1

可以使用ResultMap设置user对象的属性的映射规则。

①resultMap定义,主要是对user对象的属性设置映射规则

	<resultMap id="baseOrderMap" type="com.sangeng.pojo.Order" >
        <id column="id" property="id"></id>
        <result column="createtime" property="createtime"></result>
        <result column="price" property="price"></result>
        <result column="remark" property="remark"></result>
    </resultMap>

    <resultMap id="orderMap" type="com.sangeng.pojo.Order" autoMapping="false" extends="baseOrderMap">
        <result column="user_id" property="userId"></result>
    </resultMap>

    <!--Order和User关联的映射-->
    <resultMap id="orderUserMap" type="com.sangeng.pojo.Order" autoMapping="false" extends="orderMap">
        <result property="user.id" column="uid"></result>
        <result property="user.username" column="username"></result>
        <result property="user.age" column="age"></result>
        <result property="user.address" column="address"></result>
    </resultMap>


<!--根据订单id查询订单,要求把下单用户的信息也查询出来-->
    <select id="findById" resultMap="orderUserMap">
        SELECT
            o.`id`,o.`createtime`,o.`price`,o.`remark`,o.`user_id`,u.`id` uid,u.`username`,u.`age`,u.`address`
        FROM
            orders o,`user` u
        WHERE
            o.id = #{id} AND
            o.`user_id`=u.`id`
    </select>
方式2

可以使用ResultMap中的子标签association 来设置关联实体类的映射规则.

 	<resultMap id="baseOrderMap" type="com.sangeng.pojo.Order" >
        <id column="id" property="id"></id>
        <result column="createtime" property="createtime"></result>
        <result column="price" property="price"></result>
        <result column="remark" property="remark"></result>
    </resultMap>

    <resultMap id="orderMap" type="com.sangeng.pojo.Order" autoMapping="false" extends="baseOrderMap">
        <result column="user_id" property="userId"></result>
    </resultMap>

    <!--Order和User关联的映射(使用association)-->
    <resultMap id="orderUserMapUseAssociation" type="com.sangeng.pojo.Order" autoMapping="false" extends="orderMap">
        <association property="user" javaType="com.sangeng.pojo.User">
            <id property="id" column="uid"></id>
            <result property="username" column="username"></result>
            <result property="age" column="age"></result>
            <result property="address" column="address"></result>
        </association>
    </resultMap>

<!--根据订单id查询订单,要求把下单用户的信息也查询出来-->
    <select id="findById" resultMap="orderUserMapUseAssociation">
        SELECT
            o.`id`,o.`createtime`,o.`price`,o.`remark`,o.`user_id`,u.`id` uid,u.`username`,u.`age`,u.`address`
        FROM
            orders o,`user` u
        WHERE
            o.id = #{id} AND
            o.`user_id`=u.`id`
    </select>

一对多关系

两个实体之间是一对多的关系。(例如我们需要查询用户,要求还需要该用户所具有的角色信息。这里的用户相对于角色是一对多的。)
使用ResultMap中的collection

	<!--定义User基本属性映射规则-->
	<resultMap id="userMap" type="com.sangeng.pojo.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="age" column="age"></result>
        <result property="address" column="address"></result>
    </resultMap>
	
    <resultMap id="userRoleMap" type="com.sangeng.pojo.User"  extends="userMap">
        <collection property="roles" ofType="com.sangeng.pojo.Role" >
            <id property="id" column="rid"></id>
            <result property="name" column="name"></result>
            <result property="desc" column="desc"></result>
        </collection>
    </resultMap>


    
    <select id="findById" resultMap="userRoleMap" >
        SELECT 
            u.`id`,u.`username`,u.`age`,u.`address`,r.id rid,r.name,r.desc
        FROM 
            USER u,user_role ur,role r
        WHERE 
            u.id=ur.user_id AND ur.role_id = r.id
            AND u.id = #{id}
    </select>

分步查询

如果有需要多表查询的需求我们也可以选择用多次查询的方式来查询出我们想要的数据。Mybatis也提供了对应的配置。

​ 例如我们需要查询用户,要求还需要查询出该用户所具有的角色信息。我们可以选择先查询User表查询用户信息。然后在去查询关联的角色信息。

####实现步骤

​ 具体步骤如下:

①定义查询方法

​ 因为我们要分两步查询: 1.查询User 2.根据用户的id查询Role 所以我们需要定义下面两个方法,并且把对应的标签也先写好

1.查询User

    //根据用户名查询用户,并且要求把该用户所具有的角色信息也查询出来
    User findByUsername(String username);
    <!--根据用户名查询用户-->
    <select id="findByUsername" resultType="com.sangeng.pojo.User">
        select id,username,age,address from user where username = #{username}
    </select>

2.根据user_id查询Role

public interface RoleDao {
	//根据userId查询所具有的角色
    List<Role> findRoleByUserId(Integer userId);
}

    <!--根据userId查询所具有的角色-->
    <select id="findRoleByUserId" resultType="com.sangeng.pojo.Role">
        select 
            r.id,r.name,r.desc
        from 
            role r,user_role ur
        where 
            ur.role_id = r.id
            and ur.user_id = #{userId}
    </select>
②配置分步查询

​ 我们期望的效果是调用findByUsername方法查询出来的结果中就包含角色的信息。所以我们可以设置findByUsername方法的RestltMap,指定分步查询

    <resultMap id="userMap" type="com.sangeng.pojo.User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="age" column="age"></result>
        <result property="address" column="address"></result>
    </resultMap>
    <!--
           select属性:指定用哪个查询来查询当前属性的数据 写法:包名.接口名.方法名
           column属性:设置当前结果集中哪列的数据作为select属性指定的查询方法需要参数
       -->
	<resultMap id="userRoleMapBySelect" type="com.sangeng.pojo.User" extends="userMap">
        <collection property="roles"
                    ofType="com.sangeng.pojo.Role"
                    select="com.sangeng.dao.RoleDao.findRoleByUserId"
                    column="id">
        </collection>
    </resultMap>

​ 指定findByUsername使用我们刚刚创建的resultMap

    <!--根据用户名查询用户-->
    <select id="findByUsername" resultMap="userRoleMapBySelect">
        select id,username,age,address from user where username = #{username}
    </select>
设置按需加载

​ 我们可以设置按需加载,这样在我们代码中需要用到关联数据的时候才会去查询关联数据。

​ 有两种方式可以配置分别是全局配置和局部配置

  1. 局部配置

    设置fetchType属性为lazy

    	<resultMap id="userRoleMapBySelect" type="com.sangeng.pojo.User" extends="userMap">
            <collection property="roles"
                        ofType="com.sangeng.pojo.Role"
                        select="com.sangeng.dao.RoleDao.findRoleByUserId"
                        column="id" fetchType="lazy">
            </collection>
        </resultMap>
    
  2. 全局配置

    设置lazyLoadingEnabled为true

        <settings>
           <setting name="lazyLoadingEnabled" value="true"/>
        </settings>
    

关系总结

一方添加对象,多方添加集合
双方均可建立关系属性,在建立关系属性后,对应的MAPPer文件中需要使用<ResultMap>完成多表映射
持有对象属性使用    <association property="passport" javaType="Passport">

持有集合关系属性,使用 <collection property="students" ofType="student">

   1. JavaType  用来指定实体类中属性的类型
   2. ofType  用来指定映射到List或者集合中的 pojo类型,泛型中的约束类型!


动态sql

mybatis的映射文件中支持在基础sql上添加一些逻辑操作,并动态拼接成完整的sql之后再执行,以达到sql复用简化编程的效果

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

< sql >标签

<select id="selectUserById" resultMap="user_resultMap">


    select id,name,password,sex,regist_time from t_users where id = #{id}

  </select>

原来前面查询的这些列都可以放在< sql >标签里面被复用

<!--   抽取重复的sql片段-->
  <sql id="user_field">
    select id,name,password,sex,regist_time from t_users where
  </sql>
    <select id="selectUserById" resultType="User">
     <!--通过refid引用sql片段-->
     <include refid="user_field"></include>

      id = #{id}

  </select>

< if >标签

通过条件的是否成立来决定SQL的拼接,通过上面的案例可以看到当test判断结果为true的时候才将标签内的SQL拼接,否则不进行拼接。

public User selectUserById(@Param("id")Integer id);
public User selectUserByUsername(@Param("username")String username);

如果有很多个这种一个参数的根据不同的属性来查比较麻烦,我们可以利用动态sql写成一个

首先如果要封装成一个那么参数必须是实体类对象,规定要根据什么属性来查,就给对应的该实现赋值,然后通过if标签是否为null来判断

public User queryUser(User user);
<!--   抽取重复的sql片段-->
  <sql id="user_field">
    select id,name,password,sex,regist_time from t_users where
  </sql>


  <select id="queryUser" resultType="User">
    <include refid="user_field"></include>
    <if test="id!=null">
       id=#{id}
    </if>
    <if test="name!=null">
       name=#{name}
    </if>
  </select>
		User u=new User();

        try {
            UserDaoMapper mapper = MyBatisUtils.getMapper(UserDaoMapper.class);
           //u.setId(1);
            u.setName("dyk");
            User user = mapper.queryUser(u);
            System.out.println(user);
        }catch (Exception e)
        {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }

注意在if标签里面写属性就相当于#{}取值了

choose (when, otherwise)

这里的choose、when、otherwise中的意义相同。类似java中的 switch case块

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

< where> 标签

public List<User> queryUsers(User user);
<!--   抽取重复的sql片段-->
  <sql id="user_field">
    select id,name,password,sex,regist_time from t_users where
  </sql>

<select id="queryUsers" resultType="User">
<include refid="user_field"></include>

 <if test="id!=null">
   id=#{id}
 </if>
 <if test="name!=null">
   or name=#{name}
 </if>
  </select>
		User u=new User();

        try {
            UserDaoMapper mapper = MyBatisUtils.getMapper(UserDaoMapper.class);
            u.setId(1);
            u.setName("dyk");
             List<User> list = mapper.queryUsers(u);
             for (User user:list){
                 System.out.println(user);
             }

        }catch (Exception e)
        {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }

当测试时给id和name都赋值时查的就是同时满足id和name的所有user信息
当测试时只给id赋值时查的就是只满足id的所有user的信息
当测试时只给name时,就会报错,因为这样拼接的sql语句有问题可以通过日志打印看拼接出来的sql语句,查询时多了一个or

解决这个问题就可以使用where标签

<!--   抽取重复的sql片段-->
  <sql id="user_field">
    select id,name,password,sex,regist_time from t_users
  </sql>
  
  <select id="queryUsers" resultType="User">
<include refid="user_field"></include>
  <where>
    <if test="id!=null">
      id=#{id}
    </if>
    <if test="name!=null">
      or name=#{name}
    </if>
  </where>

  </select>

注意使用了where标签就需要把原来sql标签里的where给删掉不然会报错

where标签的作用

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除

自动补充where关键字

识别where子句中如果以 or/and开头 会自动去掉

set标签

public int updateUser(User user);
<update id="updateUser" parameterType="user">
    update t_users set
    <if test="name!=null">
      name=#{name},
    </if>
    <if test="password!=null">
      password=#{password},
    </if>
    <if test="sex!=null">
      sex=#{sex},
    </if>
    <if test="registTime!=null">

      regist_time=#{registTime}
    </if>

    where id=#{id}
  </update>
 User u=new User(1,"lfw123","123","M",new Date());

        try {
            UserDaoMapper mapper = MyBatisUtils.getMapper(UserDaoMapper.class);

            mapper.updateUser(u);
            System.out.println(u);
            MyBatisUtils.commit();
        }catch (Exception e)
        {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }

    }

如果都赋值能够正常修改,但是如果最后一个属性赋值为null的话,就还是会因为拼接出来的语句而报错,就是因为拼接出来的where前面多了一个逗号

解决使用set标签

<update id="updateUser" parameterType="user">
    update t_users
    <set>
      <if test="name!=null">
        name=#{name},
      </if>
      <if test="password!=null">
        password=#{password},
      </if>
      <if test="sex!=null">
        sex=#{sex},
      </if>
      <if test="registTime!=null">

        regist_time=#{registTime}
      </if>
    </set>

    where id=#{id}
  </update>

set标签的作用

自动补充set关键字

自动将set子语句的最后的逗号去除

注意

  • 由于我们是根据字段是否为null来拼接SQL,所以实体类的字符串字段不能初始化空串。

  • 也不能有基本数据类型,都改成对应的包装类。

trim标签有4个重要的属性:

属性描述
prefix给sql语句拼接的前缀
suffix给sql语句拼接的后缀
prefixOverrides去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定
suffixOverrides去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定
<trim prefix="" suffix="" prefixOverrides="" suffixOverrides=""> 
作用: 代替<where> <set>

这样就可以代替原来的where

 <select id="queryUsers" resultType="User">
<include refid="user_field"></include>

        <trim prefix="where"  prefixOverrides="or |and" >
          <if test="id!=null">
            id=#{id}
          </if>
          <if test="name!=null">
            or name=#{name}
          </if>
        </trim>

  </select>

注意prefixOverrides中的AND后面跟OR后面的空格不要省略

prefix="where" 在语句之前添加where关键字

prefixOverrides="or|and" where子语句中如果以or/and开头,会被去除
  <update id="updateUser" parameterType="user">
    update t_users

    <trim prefix="set" suffixOverrides=",">
      <if test="name!=null">
        name=#{name},
      </if>
      <if test="password!=null">
        password=#{password},
      </if>
      <if test="sex!=null">
        sex=#{sex},
      </if>
      <if test="registTime!=null">

        regist_time=#{registTime}
      </if>
    </trim>

    where id=#{id}
  </update>
prefix="set" 在语句之前补充一个set

suffixOverrides="," 自动将set语句最后的逗号去掉

< foreach>标签

  • item: 集合中元素迭代时的别名(必选)。

  • index: 在list和数组中,index是元素的序号,在map中,index是元素的key(可选)。

  • open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时(可选)。

  • close: foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时(可选)。

  • separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。

  • collection: 要做foreach的对象。
    批量增加和删除

public int deleteManyUser(List<Integer> ids);

public int insertManyUser(List<User> users);

<delete id="deleteManyUser" parameterType="list">
    delete from t_users where id in
    <foreach collection="list" open="(" close=")" item="num" separator=",">
      #{num}
    </foreach>
  </delete>

  <insert id="insertManyUser" parameterType="list">
    insert into t_users values
    <foreach collection="list" item="user" separator=",">
      (null,#{user.name},#{user.password},#{user.sex},#{user.registTime})
    </foreach>
  </insert>
参数描述取值
collecton容器类型list,array,map
open起始符
close结束符)
separator分隔符,
index下标号从0开始,依次递增
item当前页任意名称(循环中通过#{名称}表达式访问)

重点说明下foreach标签中的collection属性值

  • 当参数列表是单参数且为List类型时,又没有通过@param取名时,collection属性值必须为list。因为mybatis会把list封装成一个map集合,key的值固定为“list”。

  • 当参数列表是单参数且为数组类型时,又没有通过@param取名时,collection属性值必须为array。因为mybatis会把list封装成一个map集合,key的值固定为“array”。

  • 当参数列表封装到map的时候,则collection属性值没有固定值,直接设置map的key即可。

  • 如果不想使用mybaits定义的list或者array则需要我们通过@param来手动取名,也推荐这么做。

  • 当参数列表不是单参数的时候,必须通过@param来手动取名。

懒加载

当多表进行关联查询的时候比如student表跟teacher表一起全部查出来是比较耗时的,而且当我们需要查看student信息的时候根本不用去查teacher信息。所以此时,我们使用分步查询+懒加载的优势就体现出来了。

懒加载的意思就是按照需要加载。当查询student信息的时候只执行select * from student ,但当我们使用到里面的teacher信息时才去执行select * from teacher where t_id = #{t_id}。当数据量大的时候这种优势就自体现出来了。

懒加载只对关联查询起作用(一对一、一对多、多对多) 。

打开全局懒加载开关,则所有的关联查询都会进行懒加载

<setting name="lazyLoadingEnabled" value="true"/>

缓存

内存中的一块空间,服务于某个应用程序,旨在将频繁读取的数据临时保存在内存中,以便于二次快速访问

将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上查询。从缓存中查找可以提高查询效率,解决高并发系统的性能问题

在这里插入图片描述

为什么使用缓存?

减少跟数据库交互的次数,减少系统开销,提高系统效率

什么样的数据使用缓存?

经常查询并且不经常改变的数据

Mybatis缓存

  • mybatis包含了一个非常强大的查询缓存特性,它可以方便的定制和配置缓存,缓存可以极大的提高查询效率。

  • mybatis系统默认定义了两级缓存:一级缓存跟二级缓存。

    • 默认情况下,只有一级缓存开启。(SQLSession级别的缓存,也称为本地缓存)

    • 二级缓存需要手动开启跟配置,是基于namespace级别的缓存。

    • 为了提高扩展性,mybaits还定义了缓存接口,我们可以来自定义二级缓存。

一级缓存

SqlSession级别的缓存,同一个sqlsession的发起多次同构查询,将数据保存在一级缓存中
一级缓存也叫本地缓存:  SqlSession

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

注意:无需任何配置,默认开启一级缓存

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

一级缓存就是一个Map

开启日志

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

测试在一个Sqlsession中查询2次相同的记录

//		必须在同一个SqlSession中。
		SqlSession session1 = sqlSessionFactory.openSession();
		UserMapper studentMapper = session1.getMapper(UserMapper.class);
		
//     第1次查询走数据库
		User user1 = studentMapper.queryUserById(1);
		System.out.println(user1);
		
		System.out.println("=================================");
//		第2次访问获取同一个对象时直接从缓存中的user1取出来
		User user2 = studentMapper.queryUserById(1);
		System.out.println(user2);
		
//		由于user2是取的缓存中的user1,所以下面user1跟user2的equals的断言成立。
		Assert.assertEquals(user1, user2);
//		关闭SqlSession
		session1.close();

在这里插入图片描述
总结:

  • 2次必须是查询相同的数据缓存中才会有这个对象。
  • 2次的查询必须是同一个mapper,不同的mapper不会使用缓存。
  • 如果2次中间去update/insert/delete数据,即使操作的是其它数据,那么缓存就会被清空,第2次自然也是从数据库中取。
  • 我们也可以通过sqlSession.clearCache();来手动清除缓存。
  • 一级缓存默认开始,不能关闭。

二级缓存

- 二级缓存也叫全局缓存,由于一级缓存 作用域太低了,所以诞生了二级缓存。
- 基于namespace级别的缓存,也就是同一个mapper对应一个二级缓存。
- 工作机制
  - 一个会话查询的数据先放在当前的一级缓存。
  - 当会话关闭的时候一级缓存就不存在了。在关闭的时候会把缓存中的内容移到对应的namespace的二级缓存中。
  - 新的会话查询信息就会从二级缓存中获取。SqlSessionFactory级别的缓存,同一个sqlsessionFactory构建的的SqlSession发起多次同构查询,会将数据保存在二级缓存中

注意 在SqlSession.commit()或者SqlSession.close()之后失效

基于namespace级别的缓存,一个名称空间,对应一个二级缓存
不同的mapper查出的数据会放在自己对应的缓存(map)中

步骤
开启全局缓存

<!--显示的开启全局缓存-->
<setting name="cacheEnabled" value="true"/>

在要使用二级缓存的Mapper中开启
tip: 二级全局开关是默认开启的,但为了有好的可读性最好也写上。

<!--在当前Mapper.xml中使用二级缓存-->
<cache/>

可以加些参数

  <cache  eviction="FIFO"  flushInterval="60000"  size="512" readOnly="true" />
  - eviction:缓存策略

    先进先出:按对象进入缓存的顺序来移除它们。

    LRU:最近最少使用 ,移除最长时间不被使用的对象。 (默认)

  - flushInterval: 缓存保留时间

  - size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。 

  - readOnly(只读)属性可以被设置为 true 或 false。 默认是false,速度上会慢一些但是会安全。

指定Mapper缓存

<mapper namespace="com.blb.dao.UserDaoMapper">

<!--  二级缓存默认是开启的,但不是所有查询结果都会进入二级缓存-->
  <cache/>
<!--   抽取重复的sql片段-->
  <sql id="user_field">
    select id,name,password,sex,regist_time from t_users
  </sql>

 <select id="queryUsers" resultType="User">
<include refid="user_field"></include>

        <trim prefix="where"  prefixOverrides="or|and" >
          <if test="id!=null">
            id=#{id}
          </if>
          <if test="name!=null">
            or name=#{name}
          </if>
        </trim>

  </select>
 </mapper>
 SqlSession sqlSession = MyBatisUtils.getSession();
        SqlSession sqlSession1 = MyBatisUtils.getSession();
         UserDaoMapper mapper = sqlSession.getMapper(UserDaoMapper.class);
         UserDaoMapper mapper1 = sqlSession1.getMapper(UserDaoMapper.class);
         User user = new User();
        user.setName("lfw");
         List<User> list = mapper.queryUsers(user);
         sqlSession.close();//关闭sqlSession在可缓存数据
        List<User> list1 = mapper1.queryUsers(user);
        sqlSession1.close();//缓存击中

注意实体类一定要实现序列化!否则就会报错!

Caused by: java.io.NotSerializableException: com.kuang.pojo.User
public class User implements Serializable
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();

UserMapper studentMapper = session1.getMapper(UserMapper.class);
UserMapper studentMapper2 = session2.getMapper(UserMapper.class);


User user1 = studentMapper.queryUserById(1);
System.out.println(user1);
// 只有在这个session关闭的时候才会把数据从一级缓存移到二级缓存中,故需要先close再第2次读取
session1.close();  

System.out.println("=============================");

User user2= studentMapper2.queryUserById(1);
System.out.println(user2);
session2.close();

在这里插入图片描述
如果2次查询中进行了insert/update/delete操作则会清空缓存,重新去数据库查询

缓存失效的情况:

1. 查询不同的东西

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

3. 查询不同的Mapper.xml

4. 手动清理缓存

连接池

Druid是一个数据库连接池。Druid是目前最好的数据库连接池

依赖

<dependency>

    <groupId>com.alibaba</groupId>

    <artifactId>druid</artifactId>

    <version>1.1.16</version>
</dependency>

新建一个包datasource

DruidDataSourceFactory 并继承PooledDataSourceFactory 并替换数据源
package com.blb.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.datasource.pooled.PooledDataSourceFactory;

public class DruidDataSourceFactory extends PooledDataSourceFactory {
    public DruidDataSourceFactory() {
        this.dataSource=new DruidDataSource();//替换数据源
    }
}

修改mybatis.xml中连接池相关配置

<!--     POOLED配置JDBC数据源连接池-->

            <dataSource type="com.blb.datasource.DruidDataSourceFactory">
                <property name="driverClass" value="${jdbc.driver}"/>
                <property name="jdbcUrl" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>

注意

 dataSource type="你创建类的完整性类名"
  property name="driver"改为 property name="driverClass"
  property name="url"改为property name="jdbcUrl"

分页

PageHelper

概念

PageHelper是适用于Mybatis框架的一个分页插件,使用方式极为辩解,支持任何复杂的表单,多表分页查询操作

依赖

    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.1.10</version>
    </dependency>

配置mybatis.xml的配置文件

  <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--设置数据可类型Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
<!--            <property name="dialect" value="mysql"/>-->
        </plugin>
    </plugins>

注意这个标签在配置文件的位置

pageHelper应用方式

使用pageHelper提供的静态方式设置分页查询条件
PageHelper.startPage(pageNum,pageSize)
pageNum :当前页数
pageSize :一页大小
try {
            UserDaoMapper mapper = MyBatisUtils.getMapper(UserDaoMapper.class);

           //在查询前,设置分页查询的第二页,每页2条数据
            //PageHelper对其之后的第一个查询,进行分页追加功能
            PageHelper.startPage(2,2);
            List<User> list = mapper.queryAllUsers();
            for (User user:list){
                System.out.println(user);
            }
            MyBatisUtils.commit();
        } catch (Exception e) {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }

    }

Pageinfo对象

Pageinfo对象中包含了分页操作中的所有相关数据

在这里插入图片描述
使用Pageinfo保存分页查询的结果

try {
            UserDaoMapper mapper = MyBatisUtils.getMapper(UserDaoMapper.class);

           //在查询前,设置分页查询的第二页,每页2条数据
            //PageHelper对其之后的第一个查询,进行分页追加功能
            PageHelper.startPage(2,2);
            List<User> list = mapper.queryAllUsers();
            PageInfo<User> pageInfo=new PageInfo<>(list);

            for (User user:list){
                System.out.println(user);
            }

            System.out.println(pageInfo);
            MyBatisUtils.commit();
        } catch (Exception e) {
            MyBatisUtils.rollback();
            e.printStackTrace();
        }

注意事项

只有在PageHelper.startPage()方法之后的第一个查询会有执行分页

分页插件不支持带有"for update"的语句查询

分页插件不支持"嵌套查询" 由于嵌套查询结果方式会导致结果集被折叠,所以无法保证分页结果数量正确

Mybatis注解

通过在接口中直接添加MyBatis注解,完成CRUD

注意:接口注解定义完毕后,需要将接口全限定名注册到配置文件的中
经验: 注解模式属于硬编码到java文件中,失去了使用配置文件外部修改的优势,可结合需求使用

<mapper class="com.blb.dao.UserDaoMapper"></mapper>

注意dao的名字和mapper.xml文件名要一致不然会报错

关于@Param() 注解

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

查询

 @Select("select * from t_users")
public List<User> queryAllUsers();

@Select("select * from t_users where id=#{id}")
public User selectUserById(@Param("id")Integer id);

删除

@Delete(" delete from t_users where id=#{id}")
    public int deleteUser(@Param("id") Integer id);

修改

@Update(" update t_users set name=#{name},password=#{password},sex=#{sex},registTime=#{registTime} where id=#{id}")
    public int updateUser(User user);

插入

主键回填
 @Options(useGeneratedKeys = true,keyProperty = "id") //自增key主键为id
@Insert("insert into t_users(id,name,password,sex,regist_time) values (#{id} ,#{name},#{password},#{sex},#{registTime})")
public int insertUser(User user);


Lombok

一款插件可以自动帮忙生成实体类的一些方法,但是不建议使用了解即可

依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>

Lombok常用的注解

@Data:无参构造,get、set、tostring、hashcode,equals
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode
@ToString
@Getter
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值