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仓库:地址

    
    <dependency>
        <groupId>org.mybatisgroupId>
        <artifactId>mybatisartifactId>
        <version>3.5.7version>
    dependency>
    
  • 中文文档:地址

MyBatis详执行流程

Mybatis笔记_第1张图片

第一个MyBatis程序

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

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

  • 新建数据表

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

  • 导入jar包 Maven仓库

    
    <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.0modelVersion>
    
        <groupId>org.examplegroupId>
        <artifactId>testMybatisartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>15maven.compiler.source>
            <maven.compiler.target>15maven.compiler.target>
        properties>
    
    
        <dependencies>
            
            
            <dependency>
                <groupId>org.mybatisgroupId>
                <artifactId>mybatisartifactId>
                <version>3.5.7version>
            dependency>
    
            
            
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
                <version>8.0.12version>
            dependency>
    
    
            
            
            <dependency>
                <groupId>junitgroupId>
                <artifactId>junitartifactId>
                <version>4.13.2version>
                <scope>testscope>
            dependency>
    
        dependencies>
    project>
    
  • 创建子模块

2.编写MyBatis工具类

方法一

从XML中构建SqlSessionFactory

  • 编写配置文件

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="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&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 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配置文件

    
    DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="dao.UserMapper">
    
        <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 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 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 user set name=#{name},password=#{password} where id=#{id}
    
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})
                                                    
    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 map = new HashMap<>();
            map.put("userId", 4);
            map.put("userName", "wzf");
            List 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();
        }
    }

生命周期

Mybatis笔记_第2张图片 Mybatis笔记_第3张图片
  • 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中可以配置多套环境配置

作用:应对线上/测试/公司/家中 多种开发情况


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="url" value="jdbc:mysql://localhost:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&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="url" value="jdbc:mysql://localhost:3306/test1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>
    <mappers>

        <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中的&无需转义(不用写成&)
  • 引入外部配置文件
<properties resource="db.properties"/>




还可以在其中定义其他属性
<properties>
	<proerty name="" value="">proerty>
	<proerty name="" value="">proerty>
	<proerty name="" value="">proerty>
    
properties>
<environments>
    
    <environment id="test3">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver2}"/>
            <property name="url" value="${url2}"/>
            dataSource>
    environment>
environments>

  • 使用配置文件内容
<environment id="test2">
    <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>
优先级

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

设置(settings)

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

设置名 描述 有效值 默认值
cacheEnabled 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 true , false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true , false false
aggressiveLazyLoading 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。 true , false false (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled 是否允许单个语句返回多结果集(需要数据库驱动支持)。 true , false true
useColumnLabel 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 true , false true
useGeneratedKeys 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 true , false False
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 NONE, PARTIAL, FULL PARTIAL
autoMappingUnknownColumnBehavior 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException) NONE, WARNING, FAILING NONE
defaultExecutorType 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。 SIMPLE REUSE BATCH SIMPLE
defaultStatementTimeout 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 任意正整数 未设置 (null)
defaultFetchSize 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 任意正整数 未设置 (null)
defaultResultSetType 指定语句默认的滚动策略。(新增于 3.5.2) FORWARD_ONLY , SCROLL_SENSITIVE , SCROLL_INSENSITIVE , DEFAULT(等同于未设置) 未设置 (null)
safeRowBoundsEnabled 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 true , false False
safeResultHandlerEnabled 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 true , false True
mapUnderscoreToCamelCase 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 true , false False
localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 SESSION , STATEMENT SESSION
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 , false false
returnInstanceForEmptyRow 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) true , false false
logPrefix 指定 MyBatis 增加到日志名称的前缀。 任何字符串 未设置
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J , LOG4J , LOG4J2 , JDK_LOGGING , COMMONS_LOGGING , STDOUT_LOGGING , NO_LOGGING 未设置
proxyFactory 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 CGLIB , JAVASSIST JAVASSIST (MyBatis 3.3 以上)
vfsImpl 指定 VFS 的实现 自定义 VFS 的实现的类全限定名,以逗号分隔。 未设置
useActualParamName 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1) true , false true
configurationFactory 指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3) 一个类型别名或完全限定类名。 未设置
shrinkWhitespacesInSql 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5) true , false false
defaultSqlProviderType Specifies 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 name Not set

类型别名(typeAliases)

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

  • 意义:

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

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

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

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

映射器(mappers)

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

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

        <mappers>
            
            <mapper resource="dao/UserMapper.xml"/>
            
       
        mappers>
    
  • 使用映射器接口实现类的完全限定类名(!这种方法配置文件名必需与接口名相同且在同一包下)

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

        <mappers>
            
            <package name="dao"/>
        mappers>
    

驼峰命名映射(mapUnderscoreToCamelCase)

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

其他配置

  • typeHandlers(类型处理器)
  • objectFactory(对象工厂)
  • plugins(插件)
    • MyBatis Generator Core
    • MyBatis Plus

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">
            
            
            <result column="id" property="id"/>	
            <result column="name" property="name"/>
            
            
            <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的包
    
            <dependency>
                <groupId>log4jgroupId>
                <artifactId>log4jartifactId>
                <version>1.2.17version>
            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> 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();
    
  • 配置文件

    
        <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

  • 导包

    
    <dependency>
        <groupId>org.projectlombokgroupId>
        <artifactId>lombokartifactId>
        <version>1.18.20version>
        <scope>providedscope>
    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 property="teacher" column="tid" javaType="pojo.Teacher" select="getTeacher" />
        
    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"/>

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


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

使用追加的sql语句标签

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

  • 接口
//查询博客
    List<Blog> queryBlog(Map<String,String> map);
  • 配置文件

    <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

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

    
    <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标签内
  • 自动将最后一个,去除

例:


    <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)标签

  • 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片段
  • 使用标签引用

    <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>> 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<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);
      }
    }
  }
  • 结果: 进行两次查询,但只与数据库进行一次连接

Mybatis笔记_第4张图片

测试二:在两次查询中夹杂一次增添操作
  • 代码:
  @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);
      }
    }
  }
  • 结果: 与数据库进行了两次连接

Mybatis笔记_第5张图片

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

Mybatis笔记_第6张图片

小结:
  • 一级缓存默认开启
  • 只在一次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();
  }
  • 结果: 只连接一次数据库

Mybatis笔记_第7张图片

△注意

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

缓存原理

Mybatis笔记_第8张图片

自定义缓存-ehcache

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

  • 导包


<dependency>
    <groupId>org.mybatis.cachesgroupId>
    <artifactId>mybatis-ehcacheartifactId>
    <version>1.2.1version>
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的配置文件


<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    
    <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>
  
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				 内存数量最大时是否清除.

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