myBatis使用全解

 

myBatis

https://mybatis.org/

环境:

  • jdk1.8
  • maven 3.6.1
  • mysql 5.7
  • IDEA

前置知识

  • JDBC
  • mysql
  • java基础
  • Maven
  • Junit

1:简介

1.1:什么是myBatis

  • 是一款优秀的持久层框架
  • 支持定制化SQL,存储过程以及高级映射
  • MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集
  • MyBatis可以使用简单的XML或注解来配置和映射原生类型,接口,和java的POJO(普通老式java对象)为数据库中的数据

maven仓库

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>

1.2:持久化

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

2:使用MyBatis框架

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

!!!配置文件中出现中文注释要将文件头的encoding从UTF-8改为GBK

2.1:搭建环境

创建数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

create table `user`(
`id` int(20) not null PREPARE KEY,
`name` varchar(30) default null,
`pwd` varchar(30) default null
)ENGINE=INNODB DEFAULT charset = utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'海晨','123456'),
(2,'间桐樱','123456'),
(3,'缠流子','123456')	

添加依赖

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

!!!注意,maven打包的时候需要配置resources,防止无法访问配置文件

<build>
    <resources>
        <resource>
            <!--目录-->
            <directory>src/main/java</directory>
            <!--包括-->
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <!--过滤-->
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

2.2:编写配置mybatis-config.xml

<?xml version="1.0" encoding="GBK" ?>
<!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.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.198.128:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 映射表位置-->
    <mappers>
        <mapper resource="com/phc/dao/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

2.3:编写dao层接口

public interface IUserDao {
    List<User> getUserList();
}

2.4:编写映射表UserMapper.xml

<?xml version="1.0" encoding="GBK" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--  namespace是命名空间,是dao层接口的全限定位置  -->
<mapper namespace="com.phc.dao.IUserDao">
<!--    id为方法名,一定要一摸一样 resultType为结果类型-->
    <select id="getUserList" resultType="com.phc.pojo.User">
        select * from mybatis.user;
    </select>
</mapper>

2.5:编写工具类,能够获得SqlSession对象

public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory = null;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

}

2.6:测试

@Test
public void test() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    List<User> userList = userDao.getUserList();
    for (User user : userList) {
        System.out.println(user.toString());
    }
    sqlSession.close();
}

3:CURD,对Mappre.xml的解析

调用了增删改之后都要提交事务

<mapper namespace="com.phc.dao.IUserMapper">
    <select id="getUserList" resultType="com.phc.pojo.User">
        SELECT * FROM mybatis.user;
    </select>
</mapper>

3.1:namespace 命名空间

namespace的参数就i是 Dao/mapper 的全限定名称

3.2:select 标签

查询语句

  • id : namespace指向的接口下的方法

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

  • parameterType :参数类型,如果需要传递参数给mapper.xml中的sql语句,使用这个属性

    <select id="getUserById" resultType="com.phc.pojo.User" parameterType="int">
        <!-- #{id}中的id必须要与方法中的参数名称相同 -->
        select *
        from mybatis.user
        where id = #{id}
    </select>
    User getUserById(int id);
    

3.3:intsert标签

添加数据

  • <insert id="addUser" parameterType="com.phc.pojo.User">
       <!-- #{id}, #{name}, #{pwd} 中的参数都必须与pojo中的参数名称相同,也就是user的变量名称 -->
        insert into mybatis.user (id, name, pwd) value (#{id}, #{name}, #{pwd})
    </insert>
    int  addUser(User user);
    
  • 注意在调用了增删改之后都要提交事务 sqlSession.commit();

  • 方法定义时候可以返回int变量,mybatis会返回添加了多少条

3.4:update标签

修改数据

  • <update id="updateUser" parameterType="com.phc.pojo.User">
        update mybatis.user
        set name = #{name},
            pwd=#{pwd}
        where id = #{id};
    </update>
    int updateUser(User user);
    
  • 注意在调用了增删改之后都要提交事务 sqlSession.commit();

  • 方法定义时候可以返回int变量,mybatis会返回添加了多少条

3.4:delete标签

删除数据

  • <delete id="deleteUser" parameterType="int">
            delete from mybatis.user where id = #{id};
    </delete>
    int deleteUser(int id);
    
  • 注意在调用了增删改之后都要提交事务 sqlSession.commit();

  • 方法定义时候可以返回int变量,mybatis会返回添加了多少条

3.5:可能产生的错误

  • 标签匹配错误
  • resources绑定mapper,需要使用路径
  • 配置文件必须符合规范
  • NullPointerException 没有注册到资源
  • 输出的xml文件中有乱码
  • maven资源没有导出

3.6:万能的Map

如果字段过多 ,考虑使用map,或者注解

<insert id="addUser2" parameterType="map">
    insert into mybatis.user (id, name, pwd) value (#{userId}, #{userName}, #{passwd})
</insert>
    
int addUser2(Map<String,Object> map);

@Test
public void test2_1(){
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
    Map<String, Object> map = new HashMap<>();
    map.put("userId",6);
    map.put("userName","三笠");
    map.put("passwd","123456");
    int res = mapper.addUser2(map);
    if (res>0){
        System.out.println("插入条数---》"+res);
    }
    //提交事务
    sqlSession.commit();
    sqlSession.close();
}	

3.7:模糊查询

通配符最好在sql语句内写死,防止sql注入

<!-- 注意因为使用单引号将like内语句包起来了,需要使用$来应用参数,不能用# -->
<select id="getUserLike" resultType="com.phc.pojo.User">
    SELECT *
    FROM mybatis.user
    where name like '%${value}%';
</select>
List<User> getUserLike(String value);
@Test
public void test_like() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    IUserMapper userDao = sqlSession.getMapper(IUserMapper.class);
    List<User> userList = userDao.getUserLike("子");
    for (User user : userList) {
        System.out.println(user.toString());
    }
    sqlSession.close();
}

4:配置解析

4.1:核心配置文件

  • mybatis-config.xml
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

4.2:环境配置(environments)

MyBatis 可以配置成适应多种环境

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

environments default= 参数可以用于指定环境

transactionManager type= 用来指定事务的类型 JDBC 标准的数据库事务 / MANAGED 让容器管理事务的生命周期

dataSource type= 指定数据源类型 UNPOOLED无池连接 / POOLED有池连接 / JNDI 数据库由web服务器负责初始化

  • dbcp c3p0 druid 连接池,连接用完后可以回收 ,让下一个用户使用的时候无需再次连接,将没用的连接给新来的

4.3:属性(properties)

通过properties属性来实现应用配置文件,引入外部配置文件后外部配置文件的优先级更高

编写db.properties文件

// &amp;是&的转义符,如果在xml中配置是需要&amp;的,但是在db.properties文件中无需转义符,直接使用&即可
driver=com.mysql.jdbc.Driver
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://192.168.198.128:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=123456

在核心配置文件中引入

<properties resource="db.properties">
	<!-- 这里也引入了同名password属性,但是优先走外部配置,所以不生效 -->
    <property name="password" value="111111"/>
</properties>

修改dataSource

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

4.4:类型别名(typeAliases)

  • 别名是为java类设置一个短的名字,配置的时候就无需使用全限定类名了
  • 减少完全限定名的冗余

在mybatis-config.xml中配置了typeAliases 别名 后,可以直接在Mapper.xml中使用别名

    <typeAliases>
		<!-- 直接对类使用,给类设置别名  -->
        <typeAlias type="com.phc.pojo.User" alias="User"/>
        <!-- 对包名使用,回去扫描包,包下类名的驼峰名就是别名,也可以对类使用@Alias指定别名  -->
        <package name="com.phc.pojo"/>
    </typeAliases>

mapper中的语句配置,将返回类型从全限定类名改为了别名

<select id="getUserList" resultType="user">
    SELECT *
    FROM mybatis.user;
</select>

下面是一些为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格。

别名 映射的类型
_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

4.5:设置

这是 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 Removes extra whitespace characters from the SQL. Note that this also affects literal strings in SQL. (Since 3.5.5) true | false false

重要的设置

  • cacheEnabled 缓存是否开启
  • lazyLoadingEnabled 懒加载
  • useGeneratedKeys 允许jdbc支持自动生成主键
  • mapUnderscoreToCamelCase 开启驼峰命名规则(phc_name映射为phcName)
  • logImpl 指定myBatis的日志实现,未指定自动查找

4.6:映射器(Mappers)

<mappers>
    <!-- 相对路径的资源引用 -->
    <mapper resource="com/phc/dao/mapper/UserMapper.xml"/>
</mappers>
<mappers>
    <!-- 使用class来绑定映射,但是映射文件必须和接口 文件同名 同路径 -->
    <mapper class="com.phc.dao.IUserMapper"/>
</mappers>
<mappers>
    <!-- 使用扫描包来绑定映射,但是映射文件必须和接口 文件同名 同路径 -->
    <package name="com.phc.dao"/>
</mappers>

5:生命周期和作用域

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

SqlSessionFactoryBuilder

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

SqlSessionFactory

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • 多次重建 SqlSessionFactory 被视为一种代码“坏习惯”
  • 可以想象为 数据库连接池
  • 可使用单例模式和静态单例模式

SqlSession

  • 连接到连接池的一个请求

  • 实例不是线程安全的,因此不能被共享,最佳作用域是请求或者方法作用域

  • 用完就关闭,否则资源会被占用

  • 下面的示例就是一个确保 SqlSession 关闭的标准模式:

  try (SqlSession session = sqlSessionFactory.openSession()) {
    // 你的应用逻辑代码
  }

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

因为是mybatis自动对pojo填充的数据,所以必须要字段名于属性名一样

可以使用以下方法解决

  • 起别名,在sql语句中使用as将数据库字段别名设置为属性名 !!! 不推荐

    <select id="getUserLike" resultType="User">
        SELECT id, name, pwd as password
        FROM mybatis.user where name like '%${name}%';
    </select>
    <!-- 字段样式 
    	private int id;
    	private String name;
    	private String password; -->
    
  • 使用resultMap标签

    <resultMap id="UserMap" type="User" >
        <!-- column是列名,也就是字段名 property是属性名 ,使用这两个标签将二者联系起来-->
        <result column="pwd" property="password"/>
    </resultMap>
    <select id="getUserLike" resultMap="UserMap">
        SELECT *
        FROM mybatis.user where name like '%${name}%';
    </select>
    

6.1:结果映射

resultMap 元素是 MyBatis 中最重要最强大的元素。

它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来

7:日志工厂

在mybatis-config.xml中配置 来启动日志

SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

需要导包

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

7.1:log4j

  • 先用maven导包

  • 编写log4j配置文件 log4j.properties

    #将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
    log4j.rootLogger=DEBUG,console,file
      
    #控制台输出的相关设置
    log4j.appender.console = org.apache.log4j.ConsoleAppender
    log4j.appender.console.Target = System.out
    log4j.appender.console.Threshold=DEBUG
    log4j.appender.console.layout = org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=[%p][%c]-%m%n
      
    #文件输出的相关设置
    log4j.appender.file = org.apache.log4j.RollingFileAppender
    log4j.appender.file.File=./log/blog.txt
    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
    
  • <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    
  • 就可以了,框架会自动找配置文件,无需手动让配置文件生效

在类中的使用

static Logger logger = Logger.getLogger(UserDaoTest.class);
logger.info("啥子哦");

在开头实例化这个对象,调用对象方法能输出日志

8:分页查询

数据量大的时候分页查询 减少数据的处理量

使用sql语句LIMIT分页

select *from user limit 0,4
第一个参数是开始的位置,第二个参数是数据条数

limit分页

<resultMap id="UserMap" type="User">
   <result column="pwd" property="password"/>
</resultMap>
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
    select *
    from mybatis.user
    limit #{startIndex},#{pageSize};
</select>
//接口方法
List<User> getUserLike(String name);
//测试使用
@Test
public void testLimit() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
    HashMap<String, Integer> map = new HashMap<>();
    map.put("startIndex",0);
    map.put("pageSize",2);
    List<User> userByLimit = mapper.getUserByLimit(map);
    for (User user : userByLimit) {
        logger.info(user.toString());
    }
    sqlSession.close();
}

8:使用注解开发

8.1:面向接口编程

解耦,可拓展

接口的理解

  • 定义(规范,约束)与实现(名实分离原则)的分离
  • 接口的本身反映了系统设计人对系统抽象的理解
  • 接口应有2类
    • 对一个个体的抽象,对应为抽象体(abstract class)
    • 对于一个个体某一方面的抽象 形成一个抽象面(interface)、
  • 一个体可能有多个抽象面,抽象体与抽象面是有区别的

三个面向区别

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

8.2:使用注解开发

本质:反射机制实现 底层:动态代理

对接口中的方法使用注解

@Select("select * from user where id = #{id}")
User getUserById(int id);

调用该方法

@Test
public void selectUserById() {
    SqlSession sqlSession = MyBatisUtils.getSqlSession();
    IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
    User user = mapper.getUserById(1);
    logger.info(user.toString());
    sqlSession.close();
}
<!-- 其实无需在xml中配置这一条,因为注解会对方法进行自动装配,写了注解的方法直接调用即可  -->
<mapper class="com.phc.dao.IUserMapper"/>

9:动态sql

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

  • if

    <!-- 一定要注意传递进来的参数类型和字段的类型,不一样的话sql语句不能生成 -->
    <select id="getOneORAllUser" resultMap="UserMap" parameterType="map">
        SELECT * from user where 
    <!-- 在if中的语句无需使用#来获得,直接写参数名就行了 -->
        <if test="all == null">
            true;
        </if>
        <if test="all != null">
            id = #{all};
        </if>
    </select>
    
  • choose (when, otherwise)
  • trim (where, set)
  • foreach
  • 详细参阅 https://mybatis.org/mybatis-3/zh/dynamic-sql.html

10:缓存

  • 什么是缓存(Cache)
    • 在内存中的临时数据
    • 将用户经常查询过的数据放在缓存中,用户查询的时候就无需去磁盘上查询了,而是从缓存中找,提高查询效率
  • 为什么使用缓存
    • 减少和数据库的交互次数,减少系统开销,提高系统效率
  • 什么样的数据能使用缓存
    • 经常查询并且不经常改变的数据

10.1:mybatis缓存

  • mybatis包含十分强大的缓存特性,可以很方便的定制和配置缓存
  • mybatis系统中默认定义了两级缓存一级缓存二级缓存
    • 默认情况下,只有一级缓存开启
    • 二级缓存需要手动开启和配置,是基于namespace的缓存
    • 为了提高拓展性,mybatis定义了缓存接口Cache,可以使用该接口完成自定义二级缓存

10.2:一级缓存

  • 一级缓存也叫本地缓存

    • 与数据库同一次会话期间查询到的数据会放在本地缓存中
    • 如果需要获得相同数据,直接从缓存中拿,没必要查询数据库

    尝试使用不同的SqlSession

    @Test
    public void testGetOneORAllUser() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
        List<User> oneORAllUser = mapper.getOneORAllUser(null);
        for (User user : oneORAllUser) {
            logger.info(user.toString());
        }
        logger.info("============使用不同的sqlSession第二次查询==========");
        SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
        IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
        List<User> oneORAllUser1 = mapper1.getOneORAllUser(null);
        for (User user : oneORAllUser1) {
            logger.info(user.toString());
        }
        sqlSession.close();
    }
    

    查询出来的结果是有两句sql的,也使用jdbc进行了两次连接

    [DEBUG][org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [DEBUG][org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1397616978.
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-==>  Preparing: SELECT * from user where true;
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-==> Parameters: 
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-<==      Total: 8
    [INFO][com.phc.dao.UserDaoTest]-User{id=1, name='雪之下雪乃', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=2, name='间桐樱', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=3, name='缠流子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=4, name='远坂凛', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=5, name='团子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=6, name='三笠', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=7, name='土间埋', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=8, name='伊莉雅', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-============使用不同的sqlSession第二次查询==========
    [DEBUG][org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
    [DEBUG][org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1259769769.
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-==>  Preparing: SELECT * from user where true;
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-==> Parameters: 
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-<==      Total: 8
    [INFO][com.phc.dao.UserDaoTest]-User{id=1, name='雪之下雪乃', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=2, name='间桐樱', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=3, name='缠流子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=4, name='远坂凛', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=5, name='团子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=6, name='三笠', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=7, name='土间埋', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=8, name='伊莉雅', password='233333'}
    

    使用同一个sqlSession就不会有两次连接,只会有一次连接,第二次查询从缓存中自动读取

    配置 flushCache=”true” sqlSession就会自动刷新缓存,即使使用同一个sqlSession也是需要进行二次查询的

    <select id="getOneORAllUser" resultMap="UserMap" parameterType="string" flushCache="true"/>
    

  1. 在同一个 SqlSession 中, Mybatis 会把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;
  2. 不同的 SqlSession 之间的缓存是相互隔离的;
  3. 用一个 SqlSession, 可以通过配置使得在查询前清空缓存;
  4. 任何的 UPDATE, INSERT, DELETE 语句都会清空缓存。

10.3:二级缓存

  • 二级缓存也叫全局缓存,一级缓存的作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称对应一个缓存区
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这会话对应的一级缓存就没了,但是我们想要的是会话关闭后,一级缓存中的数据被保存到二级缓存中
    • 新的会话查询信息,可以直接从二级缓存中获取
    • 不同的mapper查出的数据会放在自己对应的缓存中

二级缓存存在于SqlSessionFactory 的生命周期中

步骤

  • 在mybatis配置文件中配置二级缓存—全局开关

    <settings>
        <!-- 默认为true -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    
  • 配置二级缓存—分开关,在mapper.xml中配置

    <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
    
  • 进行测试

    @Test
    public void testGetOneORAllUser() {
        SqlSession sqlSession = MyBatisUtils.getSqlSession();
        IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
        List<User> oneORAllUser = mapper.getOneORAllUser(null);
        for (User user : oneORAllUser) {
            logger.info(user.toString());
        }
        //这里对一级缓存进行了关闭,下面找不到一级缓存,就会直接去二级缓存中找
        sqlSession.close();
        logger.info("============使用不同的sqlSession第二次查询==========");
        SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
        IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
        List<User> oneORAllUser1 = mapper1.getOneORAllUser(null);
        for (User user : oneORAllUser1) {
            logger.info(user.toString());
        }
        sqlSession1.close();
        //结果是从缓存中读取的数据,并没有对数据库进行二次查询
    }
    
  • 结果

    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-==>  Preparing: SELECT * from user where true;
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-==> Parameters: 
    [DEBUG][com.phc.dao.IUserMapper.getOneORAllUser]-<==      Total: 8
    [INFO][com.phc.dao.UserDaoTest]-User{id=1, name='雪之下雪乃', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=2, name='间桐樱', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=3, name='缠流子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=4, name='远坂凛', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=5, name='团子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=6, name='三笠', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=7, name='土间埋', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=8, name='伊莉雅', password='233333'}
    [DEBUG][org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@25b485ba]
    [DEBUG][org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 632587706 to pool.
    [INFO][com.phc.dao.UserDaoTest]-============使用不同的sqlSession第二次查询==========
    [DEBUG][com.phc.dao.IUserMapper]-Cache Hit Ratio [com.phc.dao.IUserMapper]: 0.5
    [INFO][com.phc.dao.UserDaoTest]-User{id=1, name='雪之下雪乃', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=2, name='间桐樱', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=3, name='缠流子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=4, name='远坂凛', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=5, name='团子', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=6, name='三笠', password='123456'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=7, name='土间埋', password='233333'}
    [INFO][com.phc.dao.UserDaoTest]-User{id=8, name='伊莉雅', password='233333'}
    

MyBatisPlus使用提示

逻辑删除

  • 编写配置文件

    mybatis-plus:
      global-config:
        db-config:
          id-type: auto
          logic-delete-value: 1 #配置逻辑删除的全局规则
          logic-not-delete-value: 0
    
  • 使用@TableLogic标志实体类的属性,可以在这个注解下配置单独的规则,单独规则优先于全局规则

    package com.phc.phcstore.storeproduct.product.entity;
      
    import com.baomidou.mybatisplus.annotation.TableField;
    import com.baomidou.mybatisplus.annotation.TableId;
    import com.baomidou.mybatisplus.annotation.TableLogic;
    import com.baomidou.mybatisplus.annotation.TableName;
      
    import java.io.Serializable;
    import java.util.Date;
    import java.util.List;
      
    import lombok.Data;
      
    /**
     * 商品三级分类
     * 
     * @author phcbest
     * @email phcbest2017@outlook.com
     * @date 2021-06-21 17:35:50
     */
    @Data
    @TableName("pms_category")
    public class CategoryEntity implements Serializable {
    	private static final long serialVersionUID = 1L;
      
    	/**
    	 * 分类id
    	 */
    	@TableId
    	private Long catId;
    	/**
    	 * 分类名称
    	 */
    	private String name;
    	/**
    	 * 父分类id
    	 */
    	private Long parentCid;
    	/**
    	 * 层级
    	 */
    	private Integer catLevel;
    	/**
    	 * 是否显示[0-不显示,1显示]
    	 * TableLogic注解 用于表示逻辑删除
    	 */
    	@TableLogic(value = "1", delval = "0")
    	private Integer showStatus;
    	/**
    	 * 排序
    	 */
    	private Integer sort;
    	/**
    	 * 图标地址
    	 */
    	private String icon;
    	/**
    	 * 计量单位
    	 */
    	private String productUnit;
    	/**
    	 * 商品数量
    	 */
    	private Integer productCount;
      
      
    	/**
    	 * 子分类
    	 * 将表字段设置为不存在
    	 */
    	@TableField(exist = false)
    	private List<CategoryEntity> children;
      
    }
    
  • 调用mybatisplus中封装好的删除方法,删除只会修改逻辑位,不会物理删除