Mybatis笔记

Mybatis

MyBatis简介

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久层

持久层就是完成持久化工作的代码块

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

    • 数据库
    • io文件持久化
  • 持久化的作用

    • 储存要保存下来的数据
    • 降低成本(内存成本高)

MyBatis作用

  • 方便
  • 容易上手
  • 简化传统的JDBC代码,形成框架,自动化
  • 帮助程序员将数据存入到数据库中
  • 优点:
    • 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
    • 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
    • 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql。

MyBatis获取

  • Github获取 :地址

  • Maven仓库:地址

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    
  • 中文文档:地址

MyBatis详执行流程

4

第一个MyBatis程序

**思路:**搭建环境->MyBatis->编写代码->测试

1.创建目录&导包 (搭建环境)

  • 新建数据表

  • 创建Maven项目,删除src作为父工程

  • 导入jar包 Maven仓库

    <?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>testMybatis</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>15</maven.compiler.source>
            <maven.compiler.target>15</maven.compiler.target>
        </properties>
    
    
        <dependencies>
            <!-- MyBatis-->
            <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.7</version>
            </dependency>
    
            <!--  MySQL驱动-->
            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.12</version>
            </dependency>
    
    
            <!--   junit-->
            <!-- https://mvnrepository.com/artifact/junit/junit -->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    </project>
    
  • 创建子模块

2.编写MyBatis工具类

方法一

从XML中构建SqlSessionFactory

  • 编写配置文件
<?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="com.mysql.cj.jdbc.Driver"/>
<!--                <property name="driver" value="com.mysql.jdbc.Driver"/> mysql版本低于8-->
                <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>


<!--        测试开发环境-->
        <environment id="test">
            <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.xml都需要在MyBatis核心配置文件中注册-->
        <mapper resource="dao/UserMapper.xml"/>
    </mappers>
</configuration>
  • 在until中编写工具类
package 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;

public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        //配置文件url
        String resource = "mybatis-config.xml";
        try {
            //使用MyBatis获取sqlSessionFactory工具类
            InputStream in = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

        public static SqlSession getSqlSession(){
            return sqlSessionFactory.openSession();
        }
}

方法二

直接使用java代码

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

3.编写代码

  • 编写实体类

    package pojo;
    
    public class User {
        private int id;
        private String name;
        private String password;
    
        public User() {
        }
    
        public User(int id, String name, String password) {
            this.id = id;
            this.name = name;
            this.password = password;
        }
    
        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 getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    
    
  • Dao接口

    package dao;
    
    import pojo.User;
    
    import java.util.List;
    
    public interface UserMapper {
        List<User> getUserList();
    
    
    }
    
    
  • 接口实现类由原来的UserDaoImpl转变为一个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">
    <!--绑定一个对应的DAO/Mapper接口-->
    <mapper namespace="dao.UserMapper">
    <!--    Select查询语句-->
        <select id="getUserList" resultType="pojo.User">
            select * from user
        </select>
    </mapper>
    

4.测试

记得关闭SqlSession();(官方建议)

package dao;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import pojo.User;
import utils.MyBatisUtils;

import java.util.List;

public class UserDaoTest {
    @Test
    public void test(){
        //1.获得SqlSession对象
        SqlSession sqlSession =  MyBatisUtils.getSqlSession();
        //2.执行SQL
        try{
            //方法一: getMapper 最新(推荐)
            UserMapper userDao = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userDao.getUserList();
            //方法二:  旧方法
//            List<User> userList = sqlSession.selectList("dao.UserDao.getUserList");
//            sqlSession.selectMap();
//            sqlSession.selectOne();
            for (User temp : userList
            ) {
                System.out.println(temp);
            }
        }finally {
            //3.关闭sqlSession
            sqlSession.close();
        }

    }
}

增删改查CRUD

namespace 命名空间

namespace中的包名要和Dao/Mapper抽象类的命名一致

值为要绑定的抽象类

编写接口

package dao;

import pojo.User;

import java.util.List;

public interface UserMapper {
    //查询全部用户
    List<User> getUserList();

    //根据id查询用户
    List<User>getUserById(int id);

    //添加用户
    int addUser(User user);

    //修改用户
    int updateUser(User user);

    //删除用户
    int deleteUser(int id);
}

select查

选择,查询语句:

  • id:对于的namespace中的方法名 值为要绑定的接口名

  • resultType:Sql语句执行的返回值

  • parameterType:参数类型

  • useCache:是否使用缓存(可选,true为开始缓存,false为关闭缓存)

  • 例:

    @Test
        public void getUserById(){
            //1.获得SqlSession对象
            SqlSession sqlSession =  MyBatisUtils.getSqlSession();
            //2.执行SQL
            try{
                //方法一: getMapper 最新(推荐)
                UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                List<User> userList = userMapper.getUserById(1);
                //方法二:  旧方法
    //            List<User> userList = sqlSession.selectList("dao.UserDao.getUserList");
    //            sqlSession.selectMap();
    //            sqlSession.selectOne();
                for (User temp : userList
                ) {
                    System.out.println(temp);
                }
            }finally {
                //3.关闭sqlSession
                sqlSession.close();
            }
        }
    
    <select id="getUserById" resultType="pojo.User" parameterType="int">
            select * from user where id = #{id}
        </select>
    

增删改要提交事务!

sqlSession.commit();
insert增
@Test
    public void addUser(){
        //1.获得SqlSession对象
        SqlSession sqlSession =  MyBatisUtils.getSqlSession();
        //2.执行SQL
        try{
            //方法一: getMapper 最新(推荐)
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            int res = userMapper.addUser(new User(4,"wzf","44444"));
            if(res>0){
                System.out.println("插入成功!");
            }

            //提交事务
            sqlSession.commit();
        }finally {
            //3.关闭sqlSession
            sqlSession.close();
        }
    }
<insert id="addUser" parameterType="pojo.User">
        insert into user (id,name,password) values(#{id},#{name},#{password})
    </insert>
update改
 @Test
    public void updateUser(){
        //1.获得SqlSession对象
        SqlSession sqlSession =  MyBatisUtils.getSqlSession();
        //2.执行SQL
        try{
            //方法一: getMapper 最新(推荐)
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            int res = userMapper.updateUser(new User(4,"wzf","23333"));
            if(res>0){
                System.out.println("修改成功");
            }

            //提交事务
            sqlSession.commit();
        }finally {
            //3.关闭sqlSession
            sqlSession.close();
        }
    }
<update id="updateUser" parameterType="pojo.User">
        update user set name=#{name},password=#{password} where id=#{id}
    </update>
delete删
@Test
    public void deleteUser(){
        //1.获得SqlSession对象
        SqlSession sqlSession =  MyBatisUtils.getSqlSession();
        //2.执行SQL
        try{
            //方法一: getMapper 最新(推荐)
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            int res = userMapper.deleteUser(4);
            if(res>0){
                System.out.println("删除成功");
            }

            //提交事务
            sqlSession.commit();
        }finally {
            //3.关闭sqlSession
            sqlSession.close();
        }
    }
<delete id="deleteUser" parameterType="int">
        delete from user where id=#{id}
    </delete>

自定义值名称方法Map

  • 接口
int addUser2(Map<String,Object> map);
  • 配置文件
	<insert id="addUser2" parameterType="Map">
        insert into user (id,name,password) values(#{userId},#{userName},#{userPassword})
                                                    <!--此处的名称只需和Map中的key对应即可 -->
    </insert>
  • 测试程序
@Test
    public void addUser2(){
        //1.获得SqlSession对象
        SqlSession sqlSession =  MyBatisUtils.getSqlSession();
        //2.执行SQL
        try{
            //方法一: getMapper 最新(推荐)
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            Map<String,Object> map = new HashMap<>();

            map.put("userId",4);
            map.put("userName","wzf");
            map.put("userPassword","111111");

            int res = userMapper.addUser2(map);
            if(res>0){
                System.out.println("添加成功");
            }

            //提交事务
            sqlSession.commit();
        }finally {
            //3.关闭sqlSession
            sqlSession.close();
        }
    }

多个参数

方法一 使用Map封装数据
  • 接口
List<User> getUser(Map<String,Object> map);
  • 配置文件
<select id="getUser" parameterType="map" resultType="pojo.User">
        select * from user where id=#{userId} and name=#{userName}
    </select>
  • 测试代码
@Test
    public void getUser() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            Map<String, Object> map = new HashMap<>();
            map.put("userId", 4);
            map.put("userName", "wzf");
            List<User> userList = userMapper.getUser(map);

            for (User temp: userList
                 ) {
                System.out.println(temp);
            }
        } finally {
            sqlSession.close();
        }
    }
方法二 基于注解
  • 接口
List<User> getUser1(@Param("id") int id,@Param("name") String name);
  • 配置文件
<select id="getUser1" resultType="pojo.User">
        select * from user where id=#{id} and name=#{name}
    </select>
  • 测试代码
 @Test
    public void getUser1() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        try {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userMapper.getUser1(4,"wzf");

            for (User temp: userList
            ) {
                System.out.println(temp);
            }
        } finally {
            sqlSession.close();
        }
    }

模糊查询

  • 接口
	//近似查询用户
    List<User> getLikeUser(String name);
  • 配置文件
	<select id="getLikeUser" parameterType="String" resultType="pojo.User">
        select * from user where name like "%"#{name}"%"
    </select>
  • 测试
    @Test
    public void getUser2(){
        SqlSession sqlSession = MyBatisUtils.getSqlSession();

        try{
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            List<User> userList = userMapper.getLikeUser("w");
            for (User temp: userList
                 ) {
                System.out.println(temp);
            }
        }finally {
            sqlSession.close();
        }
    }

生命周期

image-20210910162138322 image-20210910162312168
  • SqlSessionFactoryBuilder:
    • 一旦创建,就不再需要它了
    • 局部变量
  • SqlSessionFactory:
    • 可以看成是连接池
    • 一旦被创建,在运行期间一直存在,没有任何理由丢弃它或者重新创建另一个实例
    • 最佳作用域:应用作用域
    • 最简单的是使用单例模式或者静态单例模式
  • SqlSession:
    • 可以看成是连接到连接池的请求
    • 不是线程安全的,不能被共享
    • 最佳作用域:请求或方法作用域
    • 用完后需要赶紧关闭,否则占用资源

MyBatis配置解析

核心配置文件

  • 官方建议命名为 mybatis-config.xml

配置文件标签先后顺序

  • properties
  • settings
  • typeAliases
  • typeHandlers
  • objectFactory
  • objectWrapperFactory
  • reflectorFactory
  • plugins
  • environments
  • databaseIdProvider
  • mappers

环境配置(environments)

MyBaits可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

  • 设置默认配置环境
<environments default="development">
配置多套环境

在配置文件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>
    <environments default="development">


<!--        默认开发环境-->
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--                <property name="driver" value="com.mysql.jdbc.Driver"/> mysql版本低于8-->
                <property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>


<!--        测试开发环境-->
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!--                <property name="driver" value="com.mysql.jdbc.Driver"/> mysql版本低于8-->
                <property name="url" value="jdbc:mysql://localhost:3306/test1?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
<!--        每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
        <mapper resource="dao/UserMapper.xml"/>
    </mappers>
</configuration>
环境配置切换

在创建sqlSessionFactory类时传入要使用的环境配置的id,不传入则使用默认环境development

		InputStream in = Resources.getResourceAsStream(resource);
     	sqlSessionFactory = new SqlSessionFactoryBuilder().build(in,"test");
		//使用testk
事务管理器 transactionManager
<transactionManager type="JDBC"/>
  • JDBC 直接使用了JDBC的提交和回滚设施,依赖从数据源获得的链接来管理事务作用域

  • MANAGED 不提交或回滚一个连接,让容器来管理事务的整个生命周期。默认情况会关闭连接。

    • 有些容器不希望被关闭时,需要将closeConnection属性设置为false来阻止默认的关闭行为

      <transactionManager type="MANAGED">
        <property name="closeConnection" value="false"/>
      </transactionManager>
      
数据源
<dataSource type="POOLED">
  • UNPOLED 无池连接
  • POOLED 有池连接(默认)
  • JNDI 用于EJB或应用服务器中

属性properties

通过properties属性来实现引用配置文件(通过.properties文件获取数据库信息)

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

注意!:此处的url中的&无需转义(不用写成&amp;)
  • 引入外部配置文件
<properties resource="db.properties"/>
<!-- 不定义其他参数可选择自闭合写法-->



还可以在其中定义其他属性
<properties>
	<proerty name="" value=""></proerty>
	<proerty name="" value=""></proerty>
	<proerty name="" value=""></proerty>
    <!-- 定义后全局可用-->
</properties>
<environments>
    <!--        测试环境3-->
    <environment id="test3">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver2}"/>
            <property name="url" value="${url2}"/>
            <property name="username" value="${username2}"
            <property name="password" value="${password2}"
        </dataSource>
    </environment>
</environments>

  • 使用配置文件内容
<environment id="test2">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <!--                <property name="driver" value="com.mysql.jdbc.Driver"/> mysql版本低于8-->
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
    </dataSource>
</environment>
优先级

外部配置文件 > 在标签中定义的

设置(settings)

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

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true , falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true , falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。true , falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true , falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true , falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。true , falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException)NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY , SCROLL_SENSITIVE , SCROLL_INSENSITIVE , DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true , falseFalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。true , falseTrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true , falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION , STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true , falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)true , falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J , LOG4J , LOG4J2 , JDK_LOGGING , COMMONS_LOGGING , STDOUT_LOGGING , NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB , JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true , falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置
shrinkWhitespacesInSql从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5)true , falsefalse
defaultSqlProviderTypeSpecifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted.A type alias or fully qualified class nameNot set

类型别名(typeAliases)

  • 类型别名是为Java类型设置一个短的名字

  • 意义:

    • 减少类完全限定名的冗余
  • 给一个类取别名(实体类少的时候使用)

	<typeAliases>
        <typeAlias type="pojo.User" alias="User"/>
    </typeAliases>
  • 可以指定一个包名,MyBatis会在包名下搜索需要的Java Bean(实体类多的时候使用)

    • 没有注解的情况下,会使用Bean的首字母小写的非限定类名来作为它的别名
    • 若有注解,则别名为其注解值 @Alias(“User”)
  • 默认别名

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

映射器(mappers)

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

  • 使用相对于类路径的资源引用

        <mappers>
            <!--        每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
            <mapper resource="dao/UserMapper.xml"/>
            <!--		可以使用通配符一次绑定多个xml文件-->
       <!-- <mapper resource="dao/*.xml"/>-->
        </mappers>
    
  • 使用映射器接口实现类的完全限定类名(!这种方法配置文件名必需与接口名相同且在同一包下)

    	<mappers>
            <!--        每一个Mapper.xml都需要在MyBatis核心配置文件中注册-->
            <mapper class="dao.UserMapper"/>
        </mappers>
    
  • 将包内的映射器接口全部注册为映射器(!这种方法配置文件名必需与接口名相同 且 在同一包下)

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

驼峰命名映射(mapUnderscoreToCamelCase)

<!--        启用驼峰命名转换   经典数据库下换线命名转换为Java驼峰命名-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>

其他配置

ResulMap

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

  • 解决方法一

    • 在sql中起别名

      	<select id="getUserList" resultType="User">
              select id,name,password AS pwd from user
          </select>
      
  • **ResulMap:**结果集映射

    <select id="getUserList1" resultMap="UserMap">
            select * from user
        </select>
        <resultMap id="UserMap" type="User">
            <!--        column对应数据库字段,property对应实体类-->
            <!--相同的可以不写-->
            <result column="id" property="id"/>	
            <result column="name" property="name"/>
            
            <!--将数据库中的password映射成User类中的pwd-->
            <result column="password" property="pwd"/>
        </resultMap>
    

日志

日志工厂

当数据库操作,出现了异常需要排错,使用日志工厂能方便排错

设置名描述有效值默认值
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J , LOG4J , LOG4J2 , JDK_LOGGING , COMMONS_LOGGING , STDOUT_LOGGING , NO_LOGGING未设置
STDOUT_LOGGING 标准日志模板
	<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
LOG4J
  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

    • 导入LOG4J的包
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
    
    • 配置LOG4J为日志的实现
    <settings>
            <setting name="logImpl" value="LOG4J"/>
        </settings>
    
    • 配置LOG4J配置

      • 在resources文件夹创建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/test.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=DEBUG
      log4j.logger.java.sql=DEBUG
      log4j.logger.java.sql.Statement=DEBUG
      log4j.logger.java.sql.ResultSet=DEBUG
      log4j.logger.java.sql.PreparedStatement=DEBUG
      
    • 高级配置

      # priority  :debug<info<warn<error
      #you cannot specify every priority with different file for log4j 
      log4j.rootLogger=debug,stdout,info,debug,warn,error 
      
      #console
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender 
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
      log4j.appender.stdout.layout.ConversionPattern= [%d{yyyy-MM-dd HH:mm:ss a}]:%p %l%m%n
      #info log
      log4j.logger.info=info
      log4j.appender.info=org.apache.log4j.DailyRollingFileAppender 
      log4j.appender.info.DatePattern='_'yyyy-MM-dd'.log'
      log4j.appender.info.File=./src/com/hp/log/info.log
      log4j.appender.info.Append=true
      log4j.appender.info.Threshold=INFO
      log4j.appender.info.layout=org.apache.log4j.PatternLayout 
      log4j.appender.info.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
      #debug log
      log4j.logger.debug=debug
      log4j.appender.debug=org.apache.log4j.DailyRollingFileAppender 
      log4j.appender.debug.DatePattern='_'yyyy-MM-dd'.log'
      log4j.appender.debug.File=./src/com/hp/log/debug.log
      log4j.appender.debug.Append=true
      log4j.appender.debug.Threshold=DEBUG
      log4j.appender.debug.layout=org.apache.log4j.PatternLayout 
      log4j.appender.debug.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
      #warn log
      log4j.logger.warn=warn
      log4j.appender.warn=org.apache.log4j.DailyRollingFileAppender 
      log4j.appender.warn.DatePattern='_'yyyy-MM-dd'.log'
      log4j.appender.warn.File=./src/com/hp/log/warn.log
      log4j.appender.warn.Append=true
      log4j.appender.warn.Threshold=WARN
      log4j.appender.warn.layout=org.apache.log4j.PatternLayout 
      log4j.appender.warn.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
      #error
      log4j.logger.error=error
      log4j.appender.error = org.apache.log4j.DailyRollingFileAppender
      log4j.appender.error.DatePattern='_'yyyy-MM-dd'.log'
      log4j.appender.error.File = ./src/com/hp/log/error.log 
      log4j.appender.error.Append = true
      log4j.appender.error.Threshold = ERROR 
      log4j.appender.error.layout = org.apache.log4j.PatternLayout
      log4j.appender.error.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss a} [Thread: %t][ Class:%c >> Method: %l ]%n%p:%m%n
      
简单使用
  • 导包 import org.apache.log4j.Logger;

  • 创建日志对象,参数为当前类的class static Logger logger = Logger.getLogger(UserMapper.class);

  • 向日志写入信息

    • info:信息

      logger.info("info:进入了testLOG4J方法");
      
    • debug:调试模式

      logger.debug("debug:进入了testLOG4J方法");
      
    • error:报错

      logger.error("error:进入了testLOG4J方法");
      
    static Logger logger = Logger.getLogger(UserMapper.class);

    @Test
    public void testLOG4J(){
        logger.info("info:进入了testLOG4J方法");
        logger.debug("debug:进入了testLOG4J方法");
        logger.error("error:进入了testLOG4J方法");

    }
}

分页

  • 分页的作用:
    • 减少数据的处理量

使用Limit分页

语法:
SELECT * FROM user LIMIT 起始数据位置,数据条数
SELECT * FROM user LIMIT 数据条数    等价于=>SELECT * FROM user LIMIT 0,数据条数

使用MyBatis实现分页

  • 接口

    //分页查询
        List<User> getUserByLimit(Map<String,Integer> map);
    
    
  • 配置文件

    <select id="getUserByLimit" resultType="pojo.User" parameterType="map">
            select *from user limit #{startIndex},#{pageSize}
        </select>
    
  • 测试

     //分页查询
        @Test
        public void getUserByLimit(){
            SqlSession sqlSession = MyBatisUtils.getSqlSession();
            try{
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            Map<String,Integer> map = new HashMap<>();
            map.put("startIndex",0);
            map.put("pageSize",10);
            List<User> userList = userMapper.getUserByLimit(map);
                for (User temp : userList
                ) {
                    System.out.println(temp);
                }
            }finally{
                sqlSession.close();
            }
        }
    

RowBounds类

通过new RowBounds(startIndex,pageSize);来处理查询后的数据实现分页

  • 接口

    List<User> getUserByBounds();
    
  • 配置文件

    <!--    分页2-->
        <select id="getUserByBounds" resultType="pojo.User">
            select * from user
        </select>
    
  • 测试

       //分页查询2
        @Test
        public void getUserByBounds(){
            SqlSession sqlSession = MyBatisUtils.getSqlSession();
            try{
                //通过java代码层面实现分类
                //通过RowBounds实现
                int startIndex = 0;
                int pageSize = 20;
                RowBounds rowBounds = new RowBounds(startIndex,pageSize);
                //selectList重载方法 .selectList(String s,Object o,RowBounds ro);
                List<User> userList =  sqlSession.selectList("dao.UserMapper.getUserByBounds",null,rowBounds );
    
          for (User user : userList) {
            System.out.println(user);
          }
            }finally{
                sqlSession.close();
            }
        }
    

分页插件PageHelper

官网

文档

使用注解开发

作用:映射简单语句

弊端:当数据库字段与类型中的字段名不同时无法获取值

使用方法

  • 编写接口

  • 将sql语句写在接口的注解中

    	@Select("SELECT * FROM user")
        List<User> getUsers();
    
  • 测试

    //    使用注解
        @Test
        public void getUsers(){
            SqlSession sqlSession = MyBatisUtils.getSqlSession();
            UserMapper02 userMapper02 = sqlSession.getMapper(UserMapper02.class);
            List<User> userList = userMapper02.getUsers();
            for (User user : userList) {
                System.out.println(user);
            }
        }
    

使用注解实现CRUD

在工具类中实现事务自动提交

sqlSessionFactory.openSession(true);
  • @Select("SELECT *FROM user where id = #{id}")
    List<User> getUserById(@Param("id") int id);
    
  • @Insert("INSERT INTO user(name,password) VALUES (#{name},#{password})")
    int addUser(@Param("name")String name,@Param("password")String password);
    
  • @Update("UPDATE user SET name=#{name},password=#{password} WHERE id = #{id}")
    int updateUser(@Param("id")int id,@Param("name")String name,@Param("password")String password);
    
  • @Delete("DELETE FROM user WHERE id=#{id}")
    int deleteUser(@Param("id")int id);
    

关于@Param()注解

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

#{}和${}区别

#能防止sql注入

$不能防止sql注入

尽量使用#

Lombok

  • 导包

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
    
    
  • @NonNull:用在方法参数前,会自动对该参数进行非空校验,为空抛出NPE(NullPointerException)

  • @Cleanup:自动管理资源,用在局部变量之前,在当前变量范围内即将执行完毕退出前会清理资源,生成try-finally的代码关闭流

  • @Getter/@Setter:用在属性上,不用自己手写setter和getter方法,还可指定访问范围

  • @ToString:用在类上,可以自动复写toString方法

  • @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法

  • @NoArgsConstructor,@RequiredArgsConstructor,@AllArgConstructor:用在类上,自动生成无参构造和使用所有参数的有参构造函数。

  • @Data:用在类上,相当于同时使用了@ToString、@EqualsAndHashCode、@Getter、@Setter和@RequiredArgsConstrutor这些注解,对POJO类十分有用。

  • @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter方法,而不提供setter方法。

  • @SneakyThrows:自动抛受检异常,而无需显示的方法上使用throws语句。

  • @Synchronized:用在方法上,将方法声明为同步的,并自动加锁。

  • @Getter(lazy=true):可以替代经典的Double Check Lock样板代码

复杂查询(包含外键的查询)

环境搭建

  • Student表
/*
 Navicat Premium Data Transfer

 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 80012
 Source Host           : localhost:3306
 Source Schema         : test

 Target Server Type    : MySQL
 Target Server Version : 80012
 File Encoding         : 65001

 Date: 16/09/2021 16:00:51
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` int(10) NOT NULL,
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  `tid` int(10) NOT NULL,
  PRIMARY KEY (`id`, `tid`) USING BTREE,
  INDEX `tid`(`tid`) USING BTREE,
  CONSTRAINT `student_ibfk_1` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, 'wzf1', 1);
INSERT INTO `student` VALUES (2, 'wzf2', 1);
INSERT INTO `student` VALUES (3, 'wzf3', 1);
INSERT INTO `student` VALUES (4, 'wzf4', 1);
INSERT INTO `student` VALUES (5, 'wzf5', 1);

SET FOREIGN_KEY_CHECKS = 1;

  • Teacher表
/*
 Navicat Premium Data Transfer

 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 80012
 Source Host           : localhost:3306
 Source Schema         : test

 Target Server Type    : MySQL
 Target Server Version : 80012
 File Encoding         : 65001

 Date: 16/09/2021 16:01:03
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher`  (
  `id` int(10) NOT NULL,
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES (1, '老师');

SET FOREIGN_KEY_CHECKS = 1;

注意!

mysql默认引擎MyISA不支持外键

可以使用InnoDB

多对一处理

复杂查询(包含外键的查询)
  • 实体类
package pojo;

import lombok.Data;

@Data
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

<!---------------------------------->
package pojo;

import lombok.Data;

@Data
public class Teacher {
    private int id;
    private String name;
}

方式一:按照查询嵌套处理(子查询)
  • 接口
List<Student> getStudent();
  • 配置文件xml
<!--    按照查询嵌套处理-->
    <select id="getStudent" resultMap="StudentTeacher">
        SELECT * FROM student
    </select>
    <resultMap id="StudentTeacher" type="pojo.Student">
        <result property="id"   column="id"/>
        <result property="name" column="name"/>
<!--        复杂属性需要单独处理
            对象:association
            集合:collection
-->
        <association property="teacher" column="tid" javaType="pojo.Teacher" select="getTeacher" />
        <!--        property对应类中的容器名   column对应的是外键名    javaType是泛型类型  select是要执行的子查询语句-->
    </resultMap>


    <select id="getTeacher" resultType="pojo.Teacher">
        SELECT * FROM student WHERE tid = #{tid}
    </select>

  • 测试
@Test
    public void testGetStudent() {
        try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            List<Student> studentList = studentMapper.getStudent();
            for (Student student : studentList) {
                System.out.println(student);
            }
        }
    }
  • 流程:

    • 调用getStudent接口
    • 返回结果
    • 进行结果集映射
    • 对teacher字段做一个子查询
  • 原理:

    • 先执行对应id查询Teacher类
    • 然后将Teacher类的字段一一映射
    • 在获取Teacher类时做一个子查询
    • 将返回的内容与Teacher类中的内容一一映射
方式二:按照结果嵌套处理
  • 接口
List<Student> getStudent2();
  • 配置文件.xml

<!--    按照结果嵌套处理-->
    <select id="getStudent2" resultMap="studentTeacher2">
        SELECT s.id sid,s.name sName,t.name tName
        FROM student s,teacher t
        WHERE s.tid=t.id;
    </select>
    <resultMap id="studentTeacher2" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sName"/>
        <association property="teacher" javaType="pojo.Teacher">
            <result property="name" column="tName"/>
        </association>

  • 测试
@Test
    public void testGetStudent2(){
        try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
            StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
            List<Student> studentList = studentMapper.getStudent2();
            for (Student student : studentList) {
                System.out.println(student);
            }
        }
    }
  • 流程

    • 调用getStudent2接口

    • 执行组合查询语句(sql中给各字段取别名,区分相同字段)

    • 进行结果集映射

    • 将Teacher作为一个类映射回去

    • 在Teacher内部再进行结果集映射

  • 原理

    • 在结果集映射中,使用association标签,将每条记录有关于teacher的信息封装成一个类返回
    • 即将查询出来的有关Teacher的字段一一对应Teacher类中的内容,然后将这个Teacher类返回

一对多处理

实体类
package pojo;

import lombok.Data;

@Data
public class Student02 {
    private int id;
    private String name;
    private int tid;

}
<!------------------------------>
package pojo;

import lombok.Data;

import java.util.List;

@Data
public class Teacher02 {
    private int id;
    private String name;

    //一个老师多个学生
    private List<Student02> students;
}

方式一:按照查询嵌套处理
  • 接口
List<Teacher02> getTeacher2(@Param("tid")int tid);
  • 配置文件
	<select id="getTeacher2" resultMap="TeacherStudent2">
        SELECT * FROM teacher WHERE id = #{tid}
    </select>
    <resultMap id="TeacherStudent2" type="pojo.Teacher02">
        <result property="id" column="id"/>
        <collection property="students" column="id" javaType="ArrayList" ofType="pojo.Student02" select="getStudentByTeacherId"/>
<!--        property对应类中的容器名   column对应的是外键名    javaType是容器类型    ofType是容器内类型(泛型)   select是要执行的子查询语句-->
    </resultMap>

    <select id="getStudentByTeacherId" resultType="pojo.Student02">
        SELECT * FROM student WHERE tid=#{tid}
    </select>
  • 测试
@Test
public void testGetTeacher2(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    try{
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher02> teacher = teacherMapper.getTeacher2(1);
        for (Teacher02 teacher02 : teacher) {
            //
            System.out.println(teacher02);
        }
    }finally {
        sqlSession.close();
    }
}
  • 流程同多对一方法一
方式二:按照结果嵌套处理
  • 接口
List<Teacher02> getTeacher(@Param("tid")int tid);
  • 配置文件.xml
<select id="getTeacher" resultMap="TeacherStudent">
    SELECT s.id sid,s.name sName,t.name tName,t.id tid
    FROM student s,teacher t
    WHERE s.tid = t.id and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="pojo.Teacher02">
    <result property="id"   column="tid"/>
    <result property="name" column="tName"/>
    <!-- 泛型类型的类型表示要用-->
    <collection property="students" ofType="pojo.Student02">
        <result property="id"   column="sid"/>
        <result property="name" column="sName"/>
        <result property="tid"  column="tid"/>
    </collection>
</resultMap>
  • 测试
@Test
public void testGetTeacher(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    try{
        TeacherMapper teacherMapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher02> teacher = teacherMapper.getTeacher(1);
        for (Teacher02 teacher02 : teacher) {
            //
            System.out.println(teacher02);
        }
    }finally {
        sqlSession.close();
    }
}
  • 结果
Teacher02(id=1, name=老师, students=[Student02(id=1, name=wzf1, tid=1), Student02(id=2, name=wzf2, tid=1), Student02(id=3, name=wzf3, tid=1), Student02(id=4, name=wzf4, tid=1), Student02(id=5, name=wzf5, tid=1)])
  • 流程

    • 调用getTeacher接口
    • 执行组合查询语句(sql中给各字段取别名,区分相同字段)
    • 进行结果集映射
    • 将Teacher的信息与Teacher类一一映射
    • 将student的全部记录都封装成一个collection返回
  • 注意

    • collection标签的类型表示要用 ofType

      <collection property="students" ofType="pojo.Student02">
      
  • 原理

    • 同多对一的方法二原理
    • 将student的信息一一对应放入student类中
    • 再将student类放入collection中返回

总结

  1. 关联 -association 多对一

  2. 集合 -collection 一对多

  3. JavaType & ofType

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

动态SQL

什么是动态SQL?

根据不同的条件生成不同的SQL语句

△此处测试启用了驼峰命名映射配置 将数据库中的create_time自动转换成实体类中的createTime变量

<!--        启用驼峰命名转换   经典数据库下换线命名转换为Java驼峰命名-->
        <setting name="mapUnderscoreToCamelCase" value="true"/>

环境搭建

  • 数据库搭建
/*
 Navicat Premium Data Transfer

 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 80012
 Source Host           : localhost:3306
 Source Schema         : test

 Target Server Type    : MySQL
 Target Server Version : 80012
 File Encoding         : 65001

 Date: 18/09/2021 17:44:39
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for blog
-- ----------------------------
DROP TABLE IF EXISTS `blog`;
CREATE TABLE `blog`  (
  `id` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '博客id',
  `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '博客标题',
  `author` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '博客作者',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

  • 实体类
package pojo;

import lombok.Data;

import java.util.Date;
@Data
public class Blog {
    private int id;
    private String title;
    private String author;
    private Date createTime;
    private int views;

    
}

  • 编写工具类
    • 使用随机生成的UUID作为主键
      • 弊端:不利于索引的建立
package utils;


import java.util.UUID;
import org.junit.Test;
public class IDUtils {
    //工具类 生成唯一随机ID
    public static String getId(){
        return UUID.randomUUID().toString().replaceAll("-","");
    }

    @Test
    public void test(){
        System.out.println(IDUtils.getId());
    }
}

if标签

使用<if test=“”>追加的sql语句</if>标签

当标签中的test中的内容成立时,就会在sql语句中追加if中的语句

  • 接口
//查询博客
    List<Blog> queryBlog(Map<String,String> map);
  • 配置文件
<!--    if语句-->
    <select id="queryBlog" parameterType="map" resultType="pojo.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 queryBlog(){
        try(SqlSession sqlSession = MyBatisUtils.getSqlSession()){
            BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
            Map<String,String> map = new HashMap();
            map.put("title","title");
            //map.put("author","wzf");

            List<Blog> blogList = blogMapper.queryBlog(map);

      for (Blog blog : blogList) {
        //
        System.out.println(blog);
      }
        }
    }

Where标签

作用:

  • 自动判断是否使用Where语句(当Where标签中的条件均不成立时不添加Where语句)
  • 自动省略第一个AND或OR

例:

<select id="queryBlog1" parameterType="map" resultType="pojo.Blog">
        SELECT * FROM blog
        <where>
            <if test="title != null">
                AND title = #{title}
            </if>
            <if test="author != null">
                AND author = #{author}
            </if>
        </where>
    </select>
  • 当title和author均为空时sql语句为: SELECT * FROM blog
  • 当title成立时: SELECT * FROM blog where title = #{title} (自动省略title前的AND)
  • 当均成立时:SELECT * FROM blog where title = #{title} AND author = #{author}

choose(when,otherwise)标签

类似Java中的Switch case结构

when 就是case

otherwise就是default

当有一个条件成立时后面的就都不执行

    <!--choose语句-->
    <select id="queryBlogChoose" parameterType="map" resultType="pojo.Blog">
        SELECT * FROM blog
        <where>
            <choose>
                <when test="title!=null">
                   AND title = #{title}
                </when>
                <when test="author!=null">
                   AND author = #{author}
                </when>
            <otherwise>		<!--默认-->
                    AND views = #{views}
            </otherwise>
            </choose>
        </where>
    </select>
  • 当title满足时: SELECT * FROM blog where title = #{title}
  • 当title和author都满足时: SELECT * FROM blog where title = #{title} (只有title=生效)
  • 当title和author都不满足时: SELECT * FROM blog where views = #{views} (执行默认条件)

set标签

  • 用在update标签内
  • 自动将最后一个,去除

例:

<!--    set-->
    <update id="updateBlog" parameterType="map">
        UPDATE blog
            <set>
                <if test="title!=null">
                    title = #{title},
                </if>
                <if test="author!=null">
                    author = #{author},
                </if>
            </set>
            WHERE id = #{id}
    </update>
  • 当title和author都存在时: UPDATE blog title = #{title} , author = #author WHERE id = #{id}
  • 当只有title存在时: UPDATE blog title = #{title} WHERE id = #{id}

trim(where,set)标签

<trim prefix="" prefixOverrides=""></trim>

  • prefix: 给sql拼接的前缀
  • suffix: 给sql拼接的后缀
  • prefixOverrides: 忽略的前缀值
  • suffixOverrides: 忽略的后缀值

例:

用trim实现where标签
<trim prefix="WHERE" prefixOverrides="AND |OR "></trim>

△AND和OR后面的空格是必须的

效果:

  • 添加前缀WHERE 删除前缀AND 或者OR
用trim实现set标签
<trim prefix="SET" suffixOverrides=","></trim>

效果:

  • 添加前缀SET 删除后缀,

SQL片段

  • 抽取公共部分,提高重用性
  • 使用<sql id="">标签抽取sql片段
  • 使用<include refid=“”>标签引用
<!--    set-->
    <update id="updateBlog" parameterType="map">
        UPDATE blog
            <set>
                <include refid="if-title-author"/>
            </set>
            WHERE id = #{id}
    </update>



    <sql id="if-title-author">
        <if test="title!=null">
            title = #{title},
        </if>
        <if test="author!=null">
            author = #{author},
        </if>
    </sql>

注意事项:

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

foreach标签

作用:对一个集合进行遍历,通常是在构建IN条件语句的时候

标签属性

  • item:集合项(迭代项)
  • index:索引变量
  • open:开头
  • separator:分隔符
  • close:结束符

例:

  • 接口
    List<Blog> queryBlogForeach(Map<String, ArrayList<Integer>> map);
  • 配置文件
    <select id="queryBlogForeach" parameterType="map" resultType="pojo.Blog">
        SELECT * FROM blog
        <where>
            <foreach collection="ids" item="id" open="AND (" separator="OR" close=")" >
                id=#{id}
            </foreach>
        </where>
    </select>
  • 测试
 @Test
  public void queryBlogForeach(){
    try(SqlSession sqlSession=MyBatisUtils.getSqlSession()){
      BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
      Map<String,ArrayList<Integer>> map = new HashMap<>();
      ArrayList<Integer> arrayList= new ArrayList<>();
      arrayList.add(1);
      arrayList.add(2);
      arrayList.add(3);
      arrayList.add(4);
      arrayList.add(5);
      map.put("ids",arrayList);
      List<Blog> blogList = blogMapper.queryBlogForeach(map);
      for (Blog blog : blogList) {
        System.out.println(blog);
      }
    }
  }
  • 通过foreach将sql拼成:SELECT * FROM blog WHERE ( id=1 OR id=2 OR id=3 OR id=4 OR id=5 )的形式

缓存

缓存:

  • 将查询的数据暂存在内存中

  • 再次查询相同的数据时,直接找缓存,提高查询效率

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

Mybatis缓存

  • Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存
    • 为了提高拓展性,MyBatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存

一级缓存

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

在sqlsession开启和关闭之间有效

  • 有效情况

    • 映射语句文件中的所有select语句的结果将会被缓存
  • 失效情况

    • 映射语句文件中所有的insert,update和delete语句将会刷新缓存
    • select语句的内容不同
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。

  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。

  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。

  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

测试
测试一:连续进行两次查询
  • 代码:
 @Test
  public void queryBlog() {
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
      BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
      Map<String, String> map = new HashMap<>();
      map.put("title", "title1");
      // map.put("author","wzf");
      //第一次查询
      List<Blog> blogList = blogMapper.queryBlog(map);
      for (Blog blog : blogList) {
        System.out.println(blog);
      }
      //第二次查询
      List<Blog> blogList1 = blogMapper.queryBlog(map);
      for (Blog blog : blogList1) {
        System.out.println(blog);
      }
    }
  }
  • 结果: 进行两次查询,但只与数据库进行一次连接

image-20211006213121698

测试二:在两次查询中夹杂一次增添操作
  • 代码:
  @Test
  public void queryBlog() {
    try (SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
      BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
      Map<String, String> map = new HashMap<>();
      map.put("title", "title1");
      // map.put("author","wzf");
      //第一次查询
      List<Blog> blogList = blogMapper.queryBlog(map);
      for (Blog blog : blogList) {
        System.out.println(blog);
      }

      //在两次查询之间插入更新语句
      this.addBlog();

      System.out.println("=======================");
      //第二次查询
      List<Blog> blogList1 = blogMapper.queryBlog(map);
      for (Blog blog : blogList1) {
        System.out.println(blog);
      }
    }
  }
  • 结果: 与数据库进行了两次连接

image-20211007000347969

  • 原因:当对数据库中的内容进行增删改以后,数据库内容改变了,为了保证数据是最新的,所有必须重写查询
手动清理缓存
sqlSession.clearCacher();	//手动清理缓存

image-20211007000846315

小结:
  • 一级缓存默认开启
  • 只在一次SqlSession中有效
作用:
  • 加速同一用户的数据库操作

二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所有诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存(在同一个mapper中)
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 会话关闭时,以及缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
开启方式:
  1. 显示的开启全局缓存

在mybatis-config.xml中显示的开启cacherEnabled设置

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

作用:↑方便他人阅读你的代码,知道你使用了缓存

  1. 在mapper文件中加入
<cache/>
<mapper namespace="dao.BlogMapper">
    <cache/>
    <insert id="addBlog" parameterType="pojo.Blog">
        INSERT INTO blog (id,title,author,create_time,views)
        VALUES (#{id},#{title},#{author},#{createTime},#{views})
    </insert>

标签属性
<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>
eviction(清除策略)
  • LRU – 最近最少使用:移除最长时间不被使用的对象。 (默认)
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
flushInterval(刷新间隔)

属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)

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

readOnly(只读)

只读的缓存会给所有调用者返回缓存对象的相同实例

测试
  • 代码:
@Test
  public void test() {
    SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
    SqlSession sqlSession2 = MyBatisUtils.getSqlSession();

    BlogMapper mapper1 = sqlSession1.getMapper(BlogMapper.class);


    Map<String, String> map = new HashMap<>();
    map.put("title", "title1");
    // map.put("author","wzf");

    List<Blog> blogList = mapper1.queryBlog(map);
    for (Blog blog : blogList) {
      System.out.println(blog);
    }
    sqlSession1.close();  //会话关闭
    //此时一级缓存将内容放入二级缓存中后,一级缓存失效



    //在不同的SqlSession中仍能使用缓存中的内容(前提:必须在同一个mapper中)
    BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
    System.out.println("=======================");

    List<Blog> blogList1 = mapper2.queryBlog(map);
    for (Blog blog : blogList1) {
      System.out.println(blog);
    }
    sqlSession2.close();
  }
  • 结果: 只连接一次数据库

image-20211007005057653

△注意

当cache中不写参数时要将java类序列化

package pojo;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;
import java.util.Objects;

@Data
public class Blog implements Serializable {
    private String id;
    private String title;
    private String author;
    private Date createTime;
    private int views;

    public Blog(String id, String title, String author, Date createTime, int views) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.createTime = createTime;
        this.views = views;
    }
}

小结:
  • 只要开启了二级缓存,在同一个mapper下就有效
  • 所有数据都会先放在一级缓存中;
  • 只有当会话提交或者结束时,才会提交到二级缓存中
select标签中可设置是否开启缓存/刷新缓存
<select id="queryBlogForeach" parameterType="map" resultType="pojo.Blog" 
        useCache="true" 	<!-- 是否使用缓存↑ -->
        flushCache="true">	<!--是否刷新缓存 -->
        SELECT * FROM blog												   
        <where>
            <foreach collection="ids" item="id" open="(" separator="OR" close=")">
                id=#{id}
            </foreach>
        </where>
    </select>
update/delete/insert标签中可设置是否刷新缓存
    <update id="updateBlog" parameterType="map" flushCache="true">	<!--是否刷新缓存-->
        UPDATE blog
        <set>
            <include refid="if-title-author"/>
        </set>
        WHERE id = #{id}
    </update>

缓存原理

未命名文件

自定义缓存-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.1</version>
</dependency>
  • 使用自定义/第三方缓存

在cache中添加type属性

<cache type=""/>
自己写的缓存实现
  • 实现Cache接口
package utils;

import org.apache.ibatis.cache.Cache;

import java.util.concurrent.locks.ReadWriteLock;

public class MyCache implements Cache {
    @Override
    public String getId() {
        return null;
    }

    @Override
    public void putObject(Object o, Object o1) {

    }

    @Override
    public Object getObject(Object o) {
        return null;
    }

    @Override
    public Object removeObject(Object o) {
        return null;
    }

    @Override
    public void clear() {

    }

    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return Cache.super.getReadWriteLock();
    }
}

  • 一般不自己实现(现在常用redis来实现)
第三方缓存Ehcache
配置文件

在resource文件夹下创建一个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.tmpiir - 默认临时文件路径
    -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <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>
  <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存是,则使用这个缓存策略。只能定义一个。-->
name                         缓存名称
maxElementInMemory           缓存最大数目
maxElementsOnDisk            硬盘最大缓存个数
eternal                      对象是否永久有效,一旦设置了,timeout将不起作用
overflowToDisk               是否保存到磁盘,当系统宕机时
timeToleSeconds              设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false 即对象不是永久有效是使用,可选属性,默认值为0
timeToLiveSeconds            设置对象在失效前的允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false 即对象不是永久有效是使用,可选属性,默认值为0
diskPersistent				 是否缓存虚拟机重启期数据
diskSpoolBufferSizeMB		 这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
diskExpiryThreadIntervalSeconds	磁盘失效线程运行时间间隔,默认是120秒
memoryStoreEvictionPolicy	 当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用的原则)
		↑可选策略还有:FIFO(先进先出),LFU(最少访问次数),LRU(最近最少使用,默认策略)
clearOnFlush				 内存数量最大时是否清除.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值