尚硅谷MyBatis 基础笔记

MyBatis 笔记

【1】配置文件

<?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">
<!-- 根标签
     根据DOCTYPE可以知道根标签的名字。
     DOCTYPE的写法是:  <!DOCTYPE 根标签 PUBLIC "唯一标记" "网络上的地址">
 -->
<configuration>

    <!-- 子标签
		可以查看DOCTYPE对应的那个dtd文件
         dtd文件一定在jar包中存在。 dtd文件所在位置,一般是factory, builder, parse, xml等。
         由上至下,第一个ELEMENT,代表根标签,语法是:  <!ELEMENT name="标签名" 标签内的可编写内容 >
         标签内的可编写内容: ,代表顺序,()代表优先级, +代表1~n个, *代表0~n个, ?代表0~1个, #PCDATA代表文本, 
						单词就是子标签的名字, EMPTY 空标签, | 选择
         其他的ELEMENT,是子标签。没有特定的顺序要求。

         标签的属性
		使用ATTLIST描述,语法是: <!ATTLIST 标签名字 标签的属性 标签属性的特征 标签的属性2 属性2的特征 。。。。。。>
         标签属性的特征: 属性的类型 属性的约束
         属性的类型: CDATA 代表字符串文本,不可以包含空格和逗号; NMTOKEN 代表字符串文本 可以包括空格和逗号; (值1 | 值2 | 值3) 枚举值
        属性的约束:#IMPLIED 可选的属性,可写可不写; #REQUIRED 必要的属性,必须写的; default 默认值是什么
     -->

    <!-- 定义properties标签。读取指定的某个配置文件
         resource属性- 从classpath开始寻址,找指定的properties配置文件,读取到当前配置文件,并使用。
         使用方式是${properties文件中的key}
     -->
    <properties resource="mysql.properties"></properties>
    
    <!-- 在当前配置文件中定义变量,变量的名是name的属性值。变量的值是value的属性值
     后续配置中使用变量的方式是: ${name的值}
 	-->
    <properties>
    	<property name="key" value="value"/>
    </properties>

    <!-- settings 配置 -->
    <settings>
        <!-- setting 某具体的配置。 name具体要配置的信息名称。官方文档有详细说明。
             value - 给对应配置设置值。

             在日志处理中,推荐使用默认规则。不配置。
             name = logImpl - 代表日志配置
             value = LOG4J - 使用log4j日志
             value = LOG4J2 - 使用log4j2日志
         -->
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <!-- 必须的配置内容,代表环境。也就是这个项目要连接的数据库在哪里?数据库的用户名密码是什么?使用的驱动类是什么。等 -->
    <!-- environments: 唯一, 代表环境,若干环境。 default属性 - 默认使用若干环境的哪一个,值是具体环境的id -->
    <environments default="mysql">
        <!-- environment 具体的一个环境, id属性,是这个环境的唯一标记 -->
        <environment id="mysql">
            <!-- transactionManager 事务管理器。采用什么方式管理事务 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- dataSource - 数据库连接池配置。数据源配置。
                 type属性: 这个数据库连接池的特性。可选值有:
                   POOLED - 池管理。创建连接池。 常用
                   UNPOOLED - 非池化管理。没有连接池。每次访问数据库,创建新的连接对象。connection
                   JNDI - java naming directory interface java命名目录接口。 某个软件或技术,提供了一个数据库连接池,
                     这个连接池,采用类似目录的方式管理,如: java:comp/mysqlDatasource。通过目录方式找这个连接池并使用。
                     不常用
             -->
            <dataSource type="POOLED">
                <!-- 配置具体的连接池特性,也就是驱动类名,数据库地址,用户名,密码 -->
                <property name="driver" value="${mysql.driver}"/>
                <property name="url" value="${mysql.url}"/>
                <property name="username" value="${mysql.username}"/>
                <property name="password" value="${mysql.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 使用mapper来通知会话工厂,有什么sql配置文件,配置文件在哪里,叫什么 -->
    <mappers>
        <!-- resource属性,从classpath开始找对应的配置文件,让会话工厂读取 -->
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>

</configuration>

【2】JDK Logging

package com.bjsxt;

import java.util.logging.*;

/**
 * JDK自带的日志 存在于java.util.logging包中
 */
public class TestJDKLogging {
    public static void main(String[] args) throws Exception {
        // 创建日志处理对象
        Logger logger = Logger.getLogger("JDKLogger");
        // 设置级别
        logger.setLevel(Level.FINEST);

        // 创建控制台日志处理器
        ConsoleHandler consoleHandler = new ConsoleHandler();
        // 设置格式
        consoleHandler.setFormatter(new SimpleFormatter());
        // 设置日志级别
        consoleHandler.setLevel(Level.FINEST);
        // 增加到日志处理器
        logger.addHandler(consoleHandler);
        
        /*// 获取控制台日志处理器
        Handler[] handlers = logger.getHandlers();
        // 数组的0下标就是控制器处理器
        Handler handler = handlers[0];
        // 设置控制台日志级别
        handler.setLevel(Level.FINEST);*/

        // 创建一个文件日志处理器, 参数就是保存日志信息的文件名
        FileHandler fileHandler = new FileHandler("jdk_log.log");
        // 设置日志内容的格式
        fileHandler.setFormatter(new SimpleFormatter());
        // 设置日志级别
        fileHandler.setLevel(Level.FINEST);
        // 把创建好的文件日志处理器,增加到日志处理对象中
        logger.addHandler(fileHandler);

        // 日志处理器,可以把日志信息输出到控制台和文件中
        logger.finest("最详细的日志");
        logger.fine("详细日志");
        logger.warning("警告日志");
        logger.info("标准消息日志");
        logger.severe("严重错误日志");
    }
}

【3】Log4j

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
# log4j中定义的级别:fatal(致命错误) > error(错误) >warn(警告) >info(普通信息) >debug(调试信息)>trace(跟踪信息)
# 定义Log4j中的Logger工具中有什么handler。 第一个DEBUG代表的是日志的级别。 后续每个单词是一个handler名称
log4j.rootLogger = DEBUG , console , D 

# log4j.logger是固定的,a.b.c是命名空间的名字可以只写一部分。
# 是mybatis中要使用日志的时候,必须提供的配置。
# 代表sql配置文件的namespace相应的sql,执行的时候,是否输出日志,及日志的级别。
# 如:namespace="a.b"  , log4j.logger.a.b=XXX级别。  那么 a.b.*所有的sql运行的时候,都输出日志。建议
# 如:namespace="a.b" , log4j.logger.a=XXX级别, 那么 a.*.*所有的sql运行,都输出日志。不建议
# 如果有多个namespace都需要输出日志。定义若干行即可
log4j.logger.a=TRACE
log4j.logger.a.b=TRACE
log4j.logger.a.c=TRACE

### console ###
# 开始定义console名称的handler
# console handler的类型
log4j.appender.console = org.apache.log4j.ConsoleAppender
# console handler输出的日志到哪里。 System.out 控制台
log4j.appender.console.Target = System.out
# console输出日志的时候,格式是什么样的。是Pattern格式。 正则表达式格式
log4j.appender.console.layout = org.apache.log4j.PatternLayout
# 具体的格式正则规则。 %p 线程|进程  %-d 时间   {yyyy-MM-dd HH\:mm\:ss} 具体的格式  %C 类名输出日志的类型名  %m 输出的日志文本  %n回车
log4j.appender.console.layout.ConversionPattern = [%p] [%-d{yyyy-MM-dd HH\:mm\:ss}] %C.%M(%L) | %m%n

### log file ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
# 文件日志handler,记录日志到哪一个文件
log4j.appender.D.File = D:/log4j.log
# 是否是追加写入到日志文件
log4j.appender.D.Append = true
# 只能升级别,不能降
# 名字是D的handler,自定义日志级别是什么。
log4j.appender.D.Threshold = INFO
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = [%p] [%-d{yyyy-MM-dd HH\:mm\:ss}] %C.%M(%L) | %m%n
package com.bjsxt;

import org.apache.log4j.Logger;

/**
 * 测试Log4j
 * 要求classpath下,必须有配置文件log4j.properties。且必须在classpath下。不能有其他目录。
 */
public class TestLog4j {
    public static void main(String[] args) {
        Logger logger = Logger.getLogger(TestLog4j.class);

        // 输出各种日志
        logger.info("info日志");
        logger.warn("waring日志");
        logger.error("error日志");
        logger.debug("debug日志");
    }
}

【4】Log4j2

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.17.2</version>
        </dependency>
<!-- Log4j2不再支持Log4j的.properties格式配置文件。而是支持.xml、.json、jsn. -->
<?xml version="1.0" encoding="utf-8" ?>
<Configuration >
    <!-- 定义输出的目的地 -->
    <Appenders>
        <!-- Console 专门用于定义控制台输出的 name:自定义名称   target:输出方式,支持SYSTEM_OUT SYSTEM_ERR -->
        <Console name="Console" target="SYSTEM_OUT">
            <!-- 输出信息表达式 -->
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
        <!-- File 专门用于定义文件输出的   fileName 文件路径   append是否允许追加 -->
        <File name="log" fileName="log/test.log" append="true">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </File>
    </Appenders>

    <Loggers>
        <!-- 控制总体级别 -->
        <!-- fatal > error > warn > info > debug > trace-->
        <Root level="debug">
            <!-- 哪个输出目的地被使用,引用Appenders里面定义的标签的name属性值 -->
            <AppenderRef ref="Console"/>
            <AppenderRef ref="log" level="info"/>
        </Root>
    </Loggers>
</Configuration>
package com.bjsxt;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 测试Log4j2
 */
public class TestLog4j2 {
    public static void main(String[] args) {
        Logger logger = LogManager.getLogger(TestLog4j2.class);

        // 输出日志
        logger.debug("debug信息");
        logger.info("info");
        logger.warn("waring");
        logger.error("error");
        logger.fatal("fatal");
    }
}

【5】SLF4j

SLF4j是日志的接口声明,不参与日志的具体实现。需要配合其他日志具体实现工具包才能进行使用。每次添加其他日志工具包时,不要忘记SLF4j整合这个日志的依赖。
SLF4J支持多种日志实现,但是在项目中整合依赖最好只有一个,否则会出现警告
<!-- 整合Log4j -->
<!-- slf4j的依赖 -->
<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>1.7.36</version>
</dependency>
<!--slf4j整合log4j的依赖,版本要和slf4j的版本对应 -->
<dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-log4j12</artifactId>
     <version>1.7.36</version>
</dependency>
<dependency>
     <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
</dependency>
<!-- 整合Log4j2 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version>
</dependency>
<!-- SLF4j整合Log4j2的依赖 -->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-slf4j-impl</artifactId>
    <version>2.17.2</version>
</dependency>
<!-- Log4j2工具的依赖-->
<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.17.2</version>
</dependency>
# Log4j配置
log4j.rootLogger=ERROR, stdout
# log4j.logger是固定的,a.b.c是命名空间的名字可以只写一部分。
log4j.logger.a.b.c=TRACE

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
public class Test {
    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(Test.class);

        logger.trace("trace");
        logger.debug("debug");
        logger.info("info");
        logger.warn("warn");
        logger.error("error");
    }
}

【6】整合日志

MyBatis框架内置日志工厂。日志工厂负责自动加载项目中配置的日志。MyBatis支持以下日志,当存在多个日志工具时,严格按照从上往下顺序使用,且只会使用一个
	- SLF4J
	- Apache Commons Logging
	- Log4j 2
	- Log4j (deprecated since 3.5.9)
	- JDK logging
在 Log4j 中,日志输出遵循两个主要规则:日志生成规则和日志输出规则

[1] 日志生成规则(由 Logger 决定):
Logger 的日志级别决定了哪些日志会被生成。
如果 rootLogger 设置为 INFO,那么默认情况下,只有 INFO 及以上级别的日志(即 INFO、WARN、ERROR、FATAL)会被生成。DEBUG 和 TRACE 级别的日志则不会被生成。

[2] 日志输出规则(由 Appender 决定):
Appender 的日志级别(通过 Threshold 设置)决定了哪些生成的日志会被输出。
Appender 只能输出那些已经生成的日志。

[3] 具体情况分析
根路径(rootLogger)设置为 INFO:意味着 INFO 及以上级别的日志会被生成,而 DEBUG 和 TRACE 级别的日志不会被生成。
console 设置为 TRACE:尽管 console 可以输出 TRACE 及以上级别的日志,但因为 rootLogger 限制了日志的生成,TRACE 和 DEBUG 级别的日志并不会被生成,因此也不会被 console 输出。

[4] 结论
在这种情况下:
最终输出到 console 的日志级别是 INFO 及以上的日志。即使 console 的 Appender 设置为 TRACE 级别,它也无法输出低于 INFO 级别的日志,因为这些日志根本没有被 rootLogger 生成。
因此,console 只会输出 INFO 及以上级别的日志,即 INFO、WARN、ERROR、FATAL,而不会输出 TRACE 或 DEBUG 级别的日志。

【7】SQL参数

<!-- 
	当参数为简单数据类型 (八大基本数据类型、String类型) 
	只有一个参数时: 可以定义任意名称的占位变量。格式:  #{任意内容} 
	有若干参数时: 可以定义任意名称的占位变量。这个占位变量名需要使用。在一个SQL语句种多个变量名不可相同。格式: #{key}
	传递参数时,使用Map集合传递。key必须和变量名一致。value就是要传递的参数值。
--> 
<?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">
<mapper namespace="a.b.c">
    <insert id="add">
        insert into people values(default,#{suiyixie})
    </insert>
</mapper>
<!-- 
   传递自定义类型变量(非简单类型变量)
   传递一个对象: 要求定义的占位变量名和自定义类型的属性名一致,大小写敏感。格式: #{对象中属性名}
   传递多个对象: 使用Map传递。key是变量一级名称,value是对象。           格式: #{key.property.property}  -->
<?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">
<mapper namespace="a.b.c">
    <insert id="insertUser3">
        insert into tb_user(id, name) values(#{u1.id}, #{u2.name})
    </insert>
</mapper>

【8】${} / #{} 区别

#{} 被解析为?,用在设置列的值或条件值时,也可以使用在分页等需要设置具体值的情况
${} 表示字符串拼接,用在动态设置表名和动态设置列名的情况下
<?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">
<mapper namespace="a.b.c">
    <insert id="insertUser4">
        insert into ${tableName} (${idColumn}, ${nameColumn}) values (#{id}, #{name})
    </insert>
</mapper>

【9】MyBatis中DML操作

操作mapper标签SqlSession方法名
新增<insert>insert(String)、insert(String,Object)
修改<update>update(String)、update(String,Object)
删除<delete>delete(String)、delete(String,Object)
在MyBatis中,增删改底层使用的逻辑是同一个。那么在没有特定需求的时候,可以使用update实现增删改的所有功能。也就是标签用update, 方法用update。
虽然方便,但不推荐。语义不明确,后期维护的时候,成本高。

【10】MyBatis中DQL操作

SqlSession方法名解释说明
selectOne()查询一行数据时,返回值为Object。如果没有查询到返回Null,但是不能查询到多行会报错。
selectList()当查询多行数据时,返回值为List。如果没有查询到返回长度为零的List对象。
selectMap()查询多行数据时,把其中某列结果当做key,每行结果为Value
selectCursor()使用游标查询时使用,在大量数据时可以代替分页
select()万能方法,需要自己定义结果处理器
package com.bjsxt.utils;

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

/**
 * 工具类型
 */
public final class MyBatisUtils {
    // 会话工厂
    private static SqlSessionFactory sqlSessionFactory;

    /**
     * 初始化代码块,只允许一次,类加载时运行
     */
    static {
        // 创建会话工厂
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.cfg.xml"));
        } catch (Exception e) {
            e.printStackTrace();
            // 抛出异常,代表工厂创建失败,后续的获取会话,访问数据库,都不可使用。
            // 让代码终止
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 私有构造方法,禁止外部创建对象
     */
    private MyBatisUtils() {
    }

    /**
     * 获取工厂的工具方法
     *
     * @return
     */
    public static SqlSessionFactory getFactory() {
        return sqlSessionFactory;
    }
}
package com.bjsxt;

import com.bjsxt.pojo.User;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.result.DefaultResultHandler;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 查询
 */
public class TestSelect {
    /**
     * 查询方法
     *
     * selectOne   selectList 最多 。 必须掌握
     * selectMap   次之。 建议掌握
     * selectCursor  再次。 了解
     * select  最少。 听说
     * 上述方法的使用频率,就是定义顺序。
     */

    /**
     * 查询多行数据
     */
    /**
     * 使用select方法查询多行数据
     * void select(String statement[, Object parameter], ResultHandler handler);
     * ResultHandler参数 - 处理查询结果的处理器。查询的结果从处理器中获取。
     * 是一个接口。只有唯一的一个方法 void handleResult(ResultContext context)
     * ResultContext中,有方法,可以获取一个对象。ResultContext,就是查询的一行数据。
     * 获取的对象,就是这行数据转换后的Java对象。
     * MyBatis给接口ResultHandler提供了若干实现类,select方法的底层实现就是selectList。
     * select方法,只能查询多行数据,且返回List集合。
     * 如果需要做特殊定制处理,可以给接口增加新的实现类。
     * 使用的ResultHandler实现类型模式是DefaultResultHandler
     * <p>
     * 使用频率最低。几乎不用。
     */
    @Test
    public void testSelect() {
        SqlSession session = MyBatisUtils.getFactory().openSession();
        DefaultResultHandler resultHandler = new DefaultResultHandler();
        session.select("user.mapper.selectAll", resultHandler);
        // 查询结果后,从ResultHandler中获取结果
        List list = resultHandler.getResultList();
        list.forEach(obj -> {
            System.out.println(obj);
        });
        session.close();
    }

    /**
     * 使用selectCursor查询多行数据
     * <T> Cursor<T> selectCursor(String statement[, Object parameter])
     * MyBatis 中 selectCursor方法返回的游标,就是JDBC查询结果游标。
     * <p>
     * 游标特性: 在MyBatis中,使用Cursor游标获取结果前,不能关闭SqlSession。否则抛出异常。
     * 1. 必须保证连接未关闭。
     * 2. 必须保证会话未结束。 会话是一个持续的,有状态的,连接或数据对象。
     * 如: 连接会话, Connection, 是一个持续的,不关闭一直可以使用。 有状态的, connection创建后,关闭前,操作都是有状态的,
     * connection是跟着操作一起变化的, 是可能有数据变动的。
     * 如: 数据会话, HttpSession。请求到来,创建会话,相应返回,连接断开,关闭。连接关闭,会话还存在。
     * <p>
     * 为什么不能关闭会话后,再迭代Cursor。
     * Cursor中数据,不再Java的管理内存中,在数据库的管理内存中。
     * 每次迭代Cursor中的一条数据,必须连接数据库,从数据库管理的内存中,把数据读取到Java管理的内存中,再使用。
     * <p>
     * Cursor - 游标
     * JDBC查询数据库时,返回的结果ResultSet中,保存查询结果数据的,就是游标。
     * 查询的SQL没有变化。
     * <p>
     * 不常用
     * 查询的数据结果数量特别多,对Java代码运行的电脑内存压力非常大,且对查询结果只做部分处理时,使用。
     */
    @Test
    public void testSelectCursor() {
        Map<String, Object> params = new HashMap<>();
        params.put("start", 100);
        params.put("end", 1000);
        SqlSession session = MyBatisUtils.getFactory().openSession();
        // 查询全部数据
        Cursor<User> cursor = session.selectCursor("user.mapper.selectAll");
        // 条件查询部分数据
        Cursor<User> cursor1 = session.selectCursor("user.mapper.selectRangeId", params);
        session.close();
        cursor.forEach(user -> {
            System.out.println(user);
        });
        cursor1.forEach(user -> {
            System.out.println(user);
        });
    }

    /**
     * 使用 selectMap 查询多行数据
     * <K,V> Map<K,V> selectMap(String statement[, Object parameter], String keyColumnName)
     * keyColumnName 参数 - 使用哪一个字段的值作为返回结果Map集合的key。
     */
    @Test
    public void testSelectMap() {
        // 使用字段name的值作为key
        SqlSession session = MyBatisUtils.getFactory().openSession();
        // 查询全部
        Map<String, User> map1 = session.selectMap("user.mapper.selectAll", "name");
        Map<String, Object> params = new HashMap<>();
        params.put("start", 100);
        params.put("end", 1000);
        // 根据主键范围查询
        Map<String, User> map2 = session.selectMap("user.mapper.selectRangeId", params, "name");
        session.close();
        System.out.println(map1.size());
        for (Map.Entry<String, User> entry : map1.entrySet()) {
            System.out.println("key = " + entry.getKey() + " ; value = " + entry.getValue());
        }
        System.out.println(map2.size());
        for (Map.Entry<String, User> entry : map2.entrySet()) {
            System.out.println("key = " + entry.getKey() + " ; value = " + entry.getValue());
        }
    }

    /**
     * 使用 selectList查询多行数据
     * <T> List<T> selectList(String statement[, Object parameter]);
     * 特性:
     * 1. 查询无结果,返回size() = 0的 集合对象
     * 2. 查询有结果,返回size() = 查询结果行数的 集合对象。
     */
    @Test
    public void testSelectAll() {
        SqlSession session = MyBatisUtils.getFactory().openSession();
        List<User> users = session.selectList("user.mapper.selectAll");
        System.out.println(users.size());
        users.forEach(user -> {
            System.out.println(user);
        });
    }

    @Test
    public void testSelectRangeId() {
        Map<String, Object> params = new HashMap<>();
        params.put("start", 10);
        params.put("end", 100);
        SqlSession session = MyBatisUtils.getFactory().openSession();
        List<User> list1 = session.selectList("user.mapper.selectRangeId", params);
        System.out.println(list1.size());
        list1.forEach(u -> {
            System.out.println(u);
        });
        params.put("start", -10);
        params.put("end", -1);
        List<User> list2 = session.selectList("user.mapper.selectRangeId", params);
        System.out.println(list2.size());
        list2.forEach(user -> {
            System.out.println(user);
        });
    }

    /**
     * 查询一行数据
     * 调用方法 : <T> T selectOne(String statement[, Object parameter])
     * 方法特性:
     * 1. 查询结果不存在,返回null
     * 2. 查询结果只有一行,返回对象
     * 3. 查询结果多于一行,抛出异常
     */
    @Test
    public void testSelectById() {
        SqlSession session = MyBatisUtils.getFactory().openSession();
        User user = session.selectOne("user.mapper.selectById", 1000);
        System.out.println(user);
        session.close();
    }

    @Test
    public void testSelectByName() {
        String name = "刘备";
        SqlSession session = MyBatisUtils.getFactory().openSession();
        User u1 = session.selectOne("user.mapper.selectByName", name);
        System.out.println(u1);
        name = "大小姐";
        User u2 = session.selectOne("user.mapper.selectByName", name);
        System.out.println(u2);
        name = "张三";
        User u3 = session.selectOne("user.mapper.selectByName", name);
        System.out.println(u3);
    }
}

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

<mapper namespace="user.mapper">

    <!-- 查询全部 -->
    <select id="selectAll" resultType="com.bjsxt.pojo.User">
        select id, name
        from tb_user
    </select>
    <select id="selectRangeId" resultType="com.bjsxt.pojo.User">
        select id, name
        from tb_user
        where id between #{start} and #{end}
    </select>

    <!-- 根据主键查询一行数据
         标签必须使用select,代表查询
         id属性,唯一标记,全局唯一。 namespace.id 全局唯一。
         必要的额外属性: resultType | resultMap 二选一。 代表返回结果的类型
         暂时只使用resultType。
         resultType - 代表查询结果的每一行数据,要转换成什么类型的Java对象。
     -->
    <select id="selectById" resultType="com.bjsxt.pojo.User">
        select id, name
        from tb_user
        where id = #{id}
    </select>

    <select id="selectByName" resultType="com.bjsxt.pojo.User">
        select id, name
        from tb_user
        where name = #{name}
    </select>
    
    <!-- 没有条件时的统计数据行数 _int 代表基本数据类型中的 int -->
    <select id="selectByPageNoParamsCount" resultType="_int">
        select count(1) from tb_student
    </select>
</mapper>

【11】分页查询

RowBounds是MyBatis中提供的一种"假分页"实现方式。对从数据库中查询到的结果进行截取。所以如果数据库中数据量特别大的时候可能会出现OOM等问题
但是由于RowBounds不需要考虑底层数据库是什么,且使用简单,所以对于一些数据量不是特别大的应用还是有人选择使用的
在SqlSession中select、selectMap、selectList中通过方法重载都提供了一个带有RowBounds
    /**
     * 分页查询:
     * 分页查询的种类|方式(面试题):
     *  1. 物理分页,使用分页查询语法实现分页查询。如:MySQL数据库中的limit; SqlServer数据库中的top; Oracle数据库中的rownum+子查询
     *     可移植性差,和数据库耦合,因为不同的数据库软件,分页语法不同。
     *     相对效率更好,因为数据库的分页是有特殊优化的,是数据库厂商优化的。且网络传输的数据少,更可靠。
     *  2. 逻辑分页,不使用分页查询语法,是查询全部数据,在内存中,使用某种逻辑,分页返回。
     *     特点和物理分页相反。
     * MyBatis中的分页实现:
     *  1. 物理分页,使用分页语法实现分页查询。
     *  2. RowBounds分页,是逻辑分页的一种实现。底层基于Cursor实现。
     *     查询后,依次遍历Cursor,按照分页的需求,找到对应的若干数据,之后关闭Cursor,返回分页结果。
     */
    /**
     * RowBounds分页
     * selectList<String statement, Object parameter, RowBounds rowBounds);
     * 不推荐使用,相对效率较低。
     */
    @Test
    public void testRowBounds() {
        SqlSession session = MyBatisUtils.getFactory().openSession();
        int page = 1;
        int rows = 3;
        RowBounds rowBounds = new RowBounds((page - 1) * rows, rows);
        List<User> users = session.selectList("user.mapper.selectAll", null, rowBounds);
        users.forEach(user -> {
            System.out.println(user);
        });
        System.out.println("====================================================");
        page = 7;
        rowBounds = new RowBounds((page - 1) * rows, rows);
        users = session.selectList("user.mapper.selectAll", null, rowBounds);
        users.forEach(user -> {
            System.out.println(user);
        });
    }
  • 功能实现
package com.bjsxt.result;

import com.bjsxt.pojo.Student;

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

/**
 * 分页查询结果类型
 */
public class PageResult implements Serializable {
    // 当前是第几页
    private int currPage;
    // 一共多少行数据
    private int total;
    // 当前页要显示的数据集合
    private List<Student> list;
    // 每页多少行数据
    private int pageSize;

    /**
     * 增加推导属性,一共多少页。 可选
     * JavaScript 中,可以使用Math.ceil(total / pageSize) 向上取整计算总计页数
     */
    public void setPages(int pages) {
    }

    public int getPages() {
        return total % pageSize == 0 ? total / pageSize : (total / pageSize + 1);
    }

    public PageResult() {
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PageResult that = (PageResult) o;
        return currPage == that.currPage && total == that.total && pageSize == that.pageSize && Objects.equals(list, that.list);
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    @Override
    public int hashCode() {
        return Objects.hash(currPage, total, list);
    }

    public int getCurrPage() {
        return currPage;
    }

    public void setCurrPage(int currPage) {
        this.currPage = currPage;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public List<Student> getList() {
        return list;
    }

    public void setList(List<Student> list) {
        this.list = list;
    }
}
package com.bjsxt.service;

import com.bjsxt.param.MyParam;
import com.bjsxt.result.PageResult;

import java.util.Map;

public interface StudentService {
    /**
     * 分页查询
     * @param param 查询条件,由Servlet处理后传入
     * @return 自定义一个结果类型返回。
     *  分页结果需要内容有:
     *   1. 第几页
     *   2. 总计多少行数据
     *   3. 一共多少页
     *   4. 当前页要显示的数据集合
     */
    PageResult selectByPage(MyParam param);
}
package com.bjsxt.service.impl;

import com.bjsxt.dao.StudentDao;
import com.bjsxt.dao.impl.StudentDaoImpl;
import com.bjsxt.param.MyParam;
import com.bjsxt.pojo.Student;
import com.bjsxt.result.PageResult;
import com.bjsxt.service.StudentService;

import java.util.List;

/**
 * 服务逻辑实现类型
 */
public class StudentServiceImpl implements StudentService {
    // 需要的数据库访问对象
    private StudentDao studentDao = new StudentDaoImpl();

    /**
     * 分页查询
     * @param param 查询条件,由Servlet处理后传入
     * @return
     */
    @Override
    public PageResult selectByPage(MyParam param) {
        // 查询当前页要显示的数据集合
        List<Student> students = studentDao.selectByPage(param);
        // 查询总计多少行数据
        int total = studentDao.selectCount(param);
        // 根据查询结果,创建要返回的结果
        PageResult pageResult = new PageResult();
        pageResult.setCurrPage(param.getPageNum());
        pageResult.setPageSize(param.getPageSize());
        pageResult.setList(students);
        pageResult.setTotal(total);

        // 返回
        return pageResult;
    }
}

package com.bjsxt.controller;

import com.bjsxt.param.MyParam;
import com.bjsxt.result.PageResult;
import com.bjsxt.service.StudentService;
import com.bjsxt.service.impl.StudentServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 分页查询学生的Servlet 控制器
 */
@WebServlet("/getStudent")
public class StudentPageServlet extends HttpServlet {
    private StudentService studentService = new StudentServiceImpl();

    /**
     * servlet 服务方法
     * 参数字符集问题:
     * 1. 检查JSP的字符集
     * 2. 检查Servlet中的请求字符集
     * 3. 检查请求方式是否是POST。request.setCharacterEncoding只对请求体中的数据生效
     * <p>
     * HTTP协议的请求头,默认字符集永远是ISO-8859-1。除非修改Tomcat中的server.xml配置文件,增加URIEncoding配置
     * 请求头参数,绝大多数处理方案是:解码再编码
     *
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        // 处理请求参数,
        // 如果未传递参数 http://localhost:80/,返回结果是null
        // 如果传递参数未赋值 http://localhost:80/pageNum=&pageSize&name&address,返回结果是空字符串 ""
        // 分页的页码
        String pageNum = req.getParameter("pageNum");
        // 分页后的每页行数
        String pageSize = req.getParameter("pageSize");
        // 姓名模糊查询条件
        String name = req.getParameter("name");
        byte[] nameBytes = name.getBytes("ISO-8859-1"); // 把字符串按照指定字符集,解码成字节数组
        name = new String(nameBytes, "UTF-8"); // 把字节数组,按照指定的字符集,编码成字符串。
        // 地址模糊查询条件
        String address = req.getParameter("address");
        address = new String(address.getBytes("ISO-8859-1"), "UTF-8");
        // 维护查询条件对象
        MyParam param = new MyParam();
        param.setName((name == null || "".equals(name) ? null : name));
        param.setAddress((address == null || "".equals(address)) ? null : address);
        param.setPageNum((pageNum == null || "".equals(pageNum)) ? 1 : Integer.parseInt(pageNum));
        param.setPageSize((pageSize == null || "".equals(pageSize)) ? 2 : Integer.parseInt(pageSize));

        // 查询
        PageResult pageResult = studentService.selectByPage(param);

        // 向客户端输出的是一个对象,使用JSON格式的字符串描述这个对象。
        // 设置响应头
        resp.setContentType("application/json; charset=UTF-8");
        // 设置相应输出流中的字符集
        resp.setCharacterEncoding("UTF-8");
        // 使用Jackson,把Java对象转换成JSON格式的字符串。
        // 创建Jackson中的对象和JSON互相转换的工具对象。
        ObjectMapper objectMapper = new ObjectMapper();
        // 把java对象,转换成JSON格式的字符串
        String json = objectMapper.writeValueAsString(pageResult);
        // PrintWriter resp.getWriter()
        // 所有的IO输出流的 PrintWriter & PrintStream,都有方法print和println
        resp.getWriter().println(json);
        // 刷新输出流的缓冲区
        resp.getWriter().flush();
    }
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
    <script src="js/jquery.js"></script>
    <script type="text/javascript">

        $(function () {
            var pageNum = 1;
            var pageSize = 2;
            var total = 0;

            getData(pageNum, pageSize, "", "");

            function getData(page, size, name, address) {
                $.ajax({
                    "type": "get",
                    "url": "/getStudent",
                    "data": {
                        "pageNum": page,
                        "pageSize": size,
                        "name": name,
                        "address": address
                    },
                    "success": function (data) {
                        pageNum = data.currPage;
                        pageSize = data.pageSize;
                        total = data.total;
                        var list = data.list;
                        var stuView = $("#stu");
                        // 清空显示视图
                        stuView.empty();
                        var body = "";
                        for (var i = 0; i < list.length; i++) {
                            var obj = list[i];
                            body += "<tr>";
                            body += "<td>";
                            body += obj.id;
                            body += "</td>";
                            body += "<td>";
                            body += obj.name;
                            body += "</td>";
                            body += "<td>";
                            body += obj.address;
                            body += "</td>";
                            body += "<td>";
                            body += "<input type='button' class='del' stuId='" + obj.id + "' value='删除'>";
                            body += "</td>";
                            body += "</tr>";
                        }
                        stuView.append(body);

                        // 增加上下页按钮
                        var pageView = $("#pageView");
                        // 清空
                        pageView.empty();
                        // 增加内部的上下页按钮
                        var pageViewBody = "";
                        if (pageNum > 1) {
                            // 有上一页
                            pageViewBody += "<input type='button' id='pre' value='上一页' οnclick='pre'>"
                        }
                        if (pageNum < data.pages) {
                            // 有下一页
                            pageViewBody += "<input type='button' id='next' value='下一页' οnclick='next()'>"
                        }
                        pageView.append(pageViewBody);
                    },
                    "dataType": "json"
                });
            }

            $("#pageView").on('click', '#pre', {}, function () {
                getData(pageNum - 1, pageSize, $("input[name=name]").val(), $("input[name=address]").val());
            });
            $("#pageView").on('click', '#next', {}, function () {
                getData(pageNum + 1, pageSize, $("input[name=name]").val(), $("input[name=address]").val());
            });
            
            $("#form").on('click', '#find', {}, function () {
                getData(1, pageSize, $("input[name=name]").val(), $("input[name=address]").val());
            })
            
            $("#stu").on('click', '.del', {}, function () {
                if (confirm("是否确认删除")) {
                    var btn = $(this);
                    $.ajax({
                        "type": "get",
                        "url": "/delete",
                        "data": "id=" + btn.attr("stuId"),
                        "success": function (data) {
                            if (data == 1) {
                                // 成功, 刷新页面
                                // 重新计算总计数据的行数和最大页码
                                total = total - 1;
                                var pages = Math.ceil(total / pageSize); // 向上取整 ,最大页码
                                if (pageNum <= pages) {
                                    // 当前页可以直接使用。
                                    getData(pageNum, pageSize, $("input[name=name]").val(), $("input[name=address]").val());
                                } else {
                                    // 当前页不可用,考虑当前页是不是第一页
                                    if (pageNum == 1) {
                                        // 第一页,没有数据了
                                        getData(1, pageSize, $("input[name=name]").val(), $("input[name=address]").val());
                                    } else {
                                        // 不是第一页,查询上一页
                                        getData(pageNum - 1, pageSize, $("input[name=name]").val(), $("input[name=address]").val());
                                    }
                                }

                            } else {
                                // 失败
                                alert("删除失败");
                            }
                        }
                    });
                }
            });
        });
    </script>
</head>
<body>
<div>
    <form id="form" action="/getStudent">
        姓名:<input type="text" name="name"> &nbsp;&nbsp;
        地址:<input type="text" name="address"> &nbsp;&nbsp;
        <input type="button" id="find" value="查询">
    </form>
</div>
<div>
    <table>
        <thead>
        <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>地址</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody id="stu"></tbody>
    </table>
</div>
<div id="pageView"></div>
</body>
</html>

【12】模糊查询

    /**
     * 模糊查询
     * SQL: select 字段 from 表格 where 字段 like ?
     * 条件是: _xxx   xxx_   _xx_   %xxx   xxx%   %xxx%   _xxx%   %xxx_
     * _ : 是一个字符
     * % : 是若干字符
     * <p>
     * MyBatis中 #{}内不可以写 '' 。所以模糊查询需要做特殊处理。
     * #{'%' + name + '%'}
     */
    @Test
    public void testLike3() {
        /*
         * 借助数据库的字符串拼接函数,实现模糊查询。
         * 缺点是: 需要了解数据库的函数,且不同的数据库函数未必相同。
         * 优点: 更加符合开发逻辑。且没有SQL注入隐患
         * 推荐的模糊查询方式
         */
        String arg = "d";
        SqlSession session = MyBatisUtils.getFactory().openSession();
        List<User> users = session.selectList("user.mapper.selectByLike3", arg);
        users.forEach(user -> {
            System.out.println(user);
        });
        session.close();
    }

    @Test
    public void testLike2() {
        /*
         * 使用MyBatis中的${}实现字符串拼接。可以避免参数传递过程中的拼接问题。
         * 但是可能有SQL注入隐患。  String arg = "d%' or 1 = 1 or name like '%n";
         */
        String arg = "d";
        SqlSession session = MyBatisUtils.getFactory().openSession();
        List<User> users = session.selectList("user.mapper.selectByLike2", arg);
        users.forEach(user -> {
            System.out.println(user);
        });
        session.close();
    }

    @Test
    public void testLike1() {
        /*
         * 需要传递参数时,拼接必要的占位符,也就是 % 或 _
         */
        String arg = "d";
        SqlSession session = MyBatisUtils.getFactory().openSession();
        List<User> users = session.selectList("user.mapper.selectByLike", "%" + arg + "%");
        users.forEach(user -> {
            System.out.println(user);
        });
        session.close();
    }
    <!-- 模糊查询 -->
    <select id="selectByLike" resultType="com.bjsxt.pojo.User">
        select id, name
        from tb_user
        where name like #{name}
    </select>
    <select id="selectByLike2" resultType="com.bjsxt.pojo.User">
        select id, name
        from tb_user
        where name like '%${name}%'
    </select>
    <select id="selectByLike3" resultType="com.bjsxt.pojo.User">
        select id, name
        from tb_user
        where name like concat('%', #{name}, '%')
    </select>

【13】别名

<?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>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
        <!-- 开启驼峰对下划线的自动映射方案。 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <!-- 定义别名 -->
    <typeAliases>
        <!-- 可以为某类型定义别名。 一个类型可以有若干别名。但是别名不能相同。
             不常用
             每个类型的别名定义,就是一个标签typeAlias
             单独为某类型定义别名时。没有大小写区分。
             常用
             如果需要定义别名的类型太多,可以基于包为若干类型一次性定义别名。
             别名就是类型的类名,这种别名大小写不敏感。
             MyBatis中有内置别名。MyBatis框架为常用类型定义了内置别名。不需要手工定义。直接使用即可。
             详细参考文档。
             常用内置别名: _int int _long long string double _double map list date
         -->
        <typeAlias type="com.bjsxt.pojo.People" alias="p1"/>
        <typeAlias type="com.bjsxt.pojo.People" alias="p2"/>
        <package name="com.bjsxt.pojo"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/PeopleMapper.xml"/>
        <mapper resource="mappers/PeopleDao.xml"/>
        <package name="com.bjsxt.mapper"/>
    </mappers>
</configuration>
  • MyBatis框架中内置了一些常见类型的别名。这些别名不需要配置
别名映射的类型别名映射的类型别名映射的类型
_bytebytestringStringdateDate
_longlongbyteBytedecimalBigDecimal
_shortshortlongLongbigdecimalBigDecimal
_intintshortShortobjectObject
_integerintintIntegermapMap
_doubledoubleintegerIntegerhashmapHashMap
_floatfloatdoubleDoublelistList
_booleanbooleanfloatFloatarraylistArrayList
booleanBooleancollectionCollection
iteratorIterator

【14】结果填充

在MyBatis框架中,查询结果处理方案(结果填充|结果映射)有若干种:
 1. 自动映射 auto mapping : 当<select resultType=自定义类型>,只要字段名称和属性名称一致,自动处理映射。 其次
    如:表格字段 id, name。类型 com.bjsxt.pojo.Xxx 属性(field和property)是 id name。自动映射。
    当字段名和属性名不同时,可以通过查询语法的别名实现自动映射。
    优势: 不需要做任何特殊的处理配置
    缺点: 要求表格的字段名称和类型的属性名称必须一致。 Java中的命名规范 驼峰。 数据库命名规范 多单词之间下划线分隔,一般所有单词全小写。
 2. 手工配置映射  ResultMap映射。  首选
    需要在SQL配置文件中定义标签 <resultMap>,并在select标签中,使用属性resultMap实现结果处理。
    <select resultMap=xxx>
    优势: 一切自定义,灵活。
    缺点: 配置增加
 3. 驼峰对下划线   很少使用
    因为Java命名习惯 驼峰; 数据库命名习惯 下划线。因为这种常见的命名习惯很通用。所以MyBatis提供自动的驼峰对下划线映射。
    需要在mybatis.cfg.xml中开启这种功能。
    优势: 按照常见命名习惯自动映射
    缺点: 要求表格命名和类型命名必须要个遵循常见命名习惯,并一一对应。

【15】接口绑定

MyBatis提供自动的接口实现能力。称为接口绑定。
命名习惯:
 每个框架或技术,都有各自的命名习惯。
 MyBatis技术中,数据访问层(持久层)接口命名习惯是 XxxMapper。
 正式工作开发中,数据访问层(持久层)接口命名习惯是 XxxDao, XxxDAO
接口绑定的规则:
 1. 必要规则: 必须遵守的规则,有两条
    1.1 SQL配置文件namespace,必须和对应的要生成实现的接口的全命名完全相同,大小写敏感。
    1.2 SQL配置文件中,编写SQL语句的标签id,必须和对应的接口中的方法名完全相同,大小写敏感。
 2. 可选规则: 可以遵守的规则,遵守可以简化mybatis.cfg.xml中的配置。 有两条
    2.1 SQL配置文件存放的位置,和对应的接口所在的包完全相同。
    2.2 SQL配置文件的文件名(不包含后缀),和对应的接口的类型名完全相同。
    遵守这两个可选规则,可以在mybatis.cfg.xml中,使用<package name="接口所在包"/>一次性配置所有的SQL配置文件。

接口绑定中的参数传递:
 1. 一个参数
    1.1 传递简单参数。 SQL配置文件中的占位变量命名随意。框架不检查。
    1.2 传递Map参数。 SQL配置文件中的占位变量名必须是Map中的key。
    1.3 传递自定义类型对象(POJO实体)。 SQL配置文件中的占位变量名必须和类型的属性名(property或field)一致。
 2. 多个参数
	注意: 使用注解了argM的形式就不能使用了,需要通过注解中名称或paramN的方式调用
    2.1 传递多个简单参数。 要求SQL配置文件中的占位变量必须按照框架要求定义。
        2.1.1 如果接口的方法参数,使用的注解@Param("")定义参数名,则占位变量名必须是注解的属性值,或param1,param2,param3等。 推荐的方式
        2.1.2 如果接口的方法参数,不使用注解,则占位变量名必须是 param1,param2或 arg0, arg1 等。不推荐的方式。面试可能问。
    2.2 传递多个参数,带有Map或自定义类型对象。 要求SQL配置文件的占位变量必须按照框架要求定义。
        2.2.1 如果接口方法参数,使用注解,可以使用 #{注解属性.key}|#{注解属性.属性名} 或者 #{param1.key} | #{param1.属性名}
        2.2.2 如果接口方法参数,不使用注解,可以使用 #{arg0.key} | #{arg0.属性名} 或者 #{param1.key} | #{param1.属性名}
  • 多参数传递
int insert1(@Param("id") Integer id, @Param("name") String name);
int insert2(Integer id, String name);
int insert3(@Param("myMap") Map<String, Object> map, @Param("peo") People people);
int insert4(Map<String, Object> map, People people);
<insert id="insert1">
    insert into tb_people(id, name) values(#{param1}, #{name})
</insert>
<insert id="insert2">
    insert into tb_people(id, name) values(#{arg0}, #{param2})
</insert>
<insert id="insert3">
    insert into tb_people(id, name) values(#{param1.id}, #{peo.name})
</insert>
<insert id="insert4">
    insert into tb_people(id, name) values(#{arg0.id}, #{param2.name})
</insert>

【16】主键回填

<!-- 高版本的MyBatis中,给insert标签增加了新的属性,简化主键回填。 常见
     useGeneratedKeys - 使用主键回填策略
     keyProperty - 主键对应的属性名
 -->
<insert id="insertByIncrement" useGeneratedKeys="true" keyProperty="id">
    <!-- 当新增语句执行后,方法结束前,运行selectKey中的SQL,查询自增的主键,并赋值到参数对象的keyProperty属性中。 通用 -->
    <!--<selectKey resultType="int" keyProperty="id" order="AFTER">
        select @@identity
    </selectKey>-->
    insert into tb_people(id, name) values(default, #{name})
</insert>

【17】动态SQL

动态SQL在MyBatis中是基于标签实现的。
 1. if标签。判断标签。根据某boolean值,或结果为boolean的表达式(布尔表达式)动态判断。
    在MyBatis的SQL配置文件中,可以写OGNL表达式。语法类似JSP中的EL表达式。在各种标签中使用,得到需要的结果。
 2. choose标签。选择标签。类似java语法中是switch case。根据多个不同的判断逻辑,选择某一个分支。
    <choose><when test=""></when><when test=""></when><when test=""></when><otherwise></otherwise></choose>
 3. trim标签。为标签体中的文本去除前后缀或增加前后缀。先删除前后缀,再增加前后缀
    <trim prefix="增加的前缀" prefixOverrides="删除的前缀" suffix="增加的后缀" suffixOverrides="删除的后缀"></trim>
 4. where标签。用在SQL语句的where子句动态处理中。如果标签中存在文本,则增加where子句。如果标签中不存在文本,则不增加where子句。
    where标签,有动态识别能力,如果标签内的文本以and或者or开头,自动删除前缀and或or。
 5. set标签。用在update语句中的标签。动态增加set子句。 有自动识别能力,如果标签内的文本以','结尾,自动删除后缀','。
 6. forEach标签。用于循环集合的标签。常用于,批量新增,范围in|not in查询等。
    要循环的变量: 命名方式有
     1. 使用注解@Param("名字"), 推荐使用
     2. arg0, collection, list 可用于List, Collection
     3. arg0, array 可用于 []
    <foreach collection="要循环的变量" open="循环内容的开头字符串"
      close="循环内容的结束字符串" item="循环过程的变量名" separator="每次循环时间隔符"></foreach>
    <foreach collection="要循环的变量" open="("
          close=")" item="a" separator=","> #{a} </foreach>
        循环遍历后的结果文本是:   (?, ?, ?)
 7. bind 标签,用于绑定修改后的特定数据的。常用于模糊查询处理。
    <bind name="定义修改后的变量名" value="OGNL表达式,做变量处理" />
 8. sql和include标签。 sql标签,定义SQL语句片段。 include标签,引用定义好的SQL语法片段。
    常用于定义字段列表,或复杂的判断逻辑。
    如:
    <sql id="xxx">数十个字段</sql>
    <sql id="yyy">复杂到不向再写一次的判断逻辑
    <where>
     十几个if,+不同的查询条件
    </where>
    </sql>
  • if
<select id="selectIf" resultType="People">
    select * from people where 1=1
    <if test="name!=null">
        and name=#{name}
    </if>
    <if test="address!=null">
        and address=#{address}
    </if>
</select>
  • choose
    <select id="selectByChoose" resultType="People">
        select id, name from tb_people
        where 1 = 1
        <if test="id != null">
            <!-- choose相当于switch -->
            <choose>
                <!-- when相当于 case语句,有具体的判断逻辑 -->
                <when test="id lt 10">
                <!-- XML文件中,小于号,必须使用实体转换 &lt; -->
                    and id &lt; 10
                </when>
                <when test="id gte 10 and id lt 20">
                    and id between 10 and 20
                </when>
                <!-- otherwise相当于default语句,代表其他情况 -->
                <otherwise>
                    and id > 20
                </otherwise>
            </choose>
        </if>
    </select>
  • trim
<select id="selectIf" resultType="People">
    select * from people
    <!-- <trim prefix="增加的前缀" prefixOverrides="删除的前缀" suffix="增加的后缀" suffixOverrides="删除的后缀"></trim> -->
    <trim prefix="where" prefixOverrides="and">
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="address!=null">
            and address=#{address}
        </if>
    </trim>
</select>
  • where
<select id="selectIf" resultType="People">
    select * from people
    <where>
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="address!=null">
            and address=#{address}
        </if>
    </where>
</select>
  • set
<update id="update">
    update people
    <set>
        <if test="name!=null">
            name=#{name},
        </if>
        <if test="address!=null">
            address=#{address},
        </if>
        id=#{id}
    </set>
    where id = #{id}
</update>
  • foreach
<select id="selectByForEach" resultType="People">
    select id, name from tb_people where id in
    <foreach collection="ids" open="(" close=")" item="id" separator=",">
        #{id}
    </foreach>
</select>

<insert id="insertBatch">
    insert into tb_people(id, name)

    <foreach collection="collection" open="values(" close=")" item="p" separator="),(">
        #{p.id},#{p.name}
    </foreach>
</insert>
/**
 * 根据参数,in查询部分数据
 *
 * @param ids
 * @return
 */
List<People> selectByForEach(@Param("ids") List<Integer> ids);

/**
 * 批量新增
 *
 * @param list
 * @return
 */
int insertBatch(List<People> list);
  • bind
<!-- 映射文件中需要注意value属性值中进行字符串拼接。%两次要有单引号,name没有单引号 -->
<select id="selectLike" resultType="People">
    <bind name="n" value="'%'+name+'%'"/>
    select * from people where name like #{n}
</select>
  • sql 和 include
<select id="selectSQL" resultType="People">
	select <include refid="mysqlpart"></include> from people
</select>
<sql id="mysqlpart">
	id,name,address
</sql>

【18】常用注解

注解功能
@Insert新增
@Delete删除
@Update修改
@Select查询
@Results / @Result结果映射
@SelectKey主键回填
@InsertProvider调用SQL构建器。新增专用
@DeleteProvider调用SQL构建器。删除专用
@UpdateProvider调用SQL构建器。修改专用
@SelectProvider调用SQL构建器。查询专用
@Param定义参数的名称
常用注解:
 1. 新增
    @Insert("sql语句")
    注解中处理参数的方式,和SQL配置文件中的方式完全相同。但是此注解没有动态SQL标签的功能。
    1.1 主键回填
    @SelectKey(statement="sql", before=真假, keyProperty="主键属性名"[, keyColumn="查询主键的字段名"], resultType=类型)
 2. 更新
    @Update("sql"), 和SQL配置文件采用相同的参数处理方案。
 3. 删除
    @Delete("sql")
 4. 查询
    @Select("sql")
    使用注解时,返回结果类型,默认就是方法的返回值类型或返回值集合的泛型或Map。 相当于SQL配置文件中的<select resultType>配置方式
 5. 动态新增
    @InsertProvider(type=Xxx.class, method="生成SQL语句的方法名")
 6. 动态更新
    @UpdateProvider()
 7. 动态删除
    @DeleteProvider()
 8. 动态查询
    @SelectProvider()
 9. 结果映射处理
    @Results(  //相当于resultMap标签, 没有强制id, 类型就是方法返回类型或返回集合的泛型
      @Result(column="字段名" property="属性名"), // 配置类型中的一个属性和一个字段的映射
      @Result(column="字段名" property="属性名")
    )

使用注解:
 1. 前提: 在mybatis.cfg.xml配置文件中,使用<mapper class="" />或者<package name="" />配置。
    <mapper class="" /> 配置MyBatis框架扫描到的数据访问层接口类型, XxxMapper, XxxDao。
    <package name="" /> 配置MyBatis框架扫描数据访问层接口类型的包,使用报名定义规则赋值  x.y.z
 2. 日志: 使用接口的包作为日志输出的定位即可。更精确的方式是包名.接口名。
<?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>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <!-- 定义别名 -->
    <typeAliases>
        <package name="com.bjsxt.pojo"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 精确配置数据访问接口类型 -->
        <mapper class="com.bjsxt.mapper.DeptMapper"/>
    </mappers>
</configuration>
create table tb_dept(
  dep_id int(11) primary key auto_increment  comment '部门主键',
  dep_name varchar(32)  comment '部门名称',
  dep_addr varchar(255) comment '部门地址'
) comment '部门表';

insert into tb_dept(dep_id, dep_name, dep_addr)
  values (default, '教学部', '赛蒂工业园'),
         (default, '行政部', '赛蒂工业园'),
         (default, '财务部', '赛蒂工业园');
package com.bjsxt.utils;

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

/**
 * 工具类型
 */
public final class MyBatisUtils {
    // 会话工厂
    private static SqlSessionFactory sqlSessionFactory;

    /**
     * 初始化代码块,只允许一次,类加载时运行
     */
    static {
        // 创建会话工厂
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.cfg.xml"));
        } catch (Exception e) {
            e.printStackTrace();
            // 抛出异常,代表工厂创建失败,后续的获取会话,访问数据库,都不可使用。
            // 让代码终止
            throw new ExceptionInInitializerError(e);
        }
    }

    /**
     * 私有构造方法,禁止外部创建对象
     */
    private MyBatisUtils() {
    }

    /**
     * 获取工厂的工具方法
     *
     * @return
     */
    public static SqlSessionFactory getFactory() {
        return sqlSessionFactory;
    }
}
package com.bjsxt.mapper;

import com.bjsxt.pojo.Dept;
import com.bjsxt.provider.MyInsertSqlProvider;
import org.apache.ibatis.annotations.*;

import java.util.List;
import java.util.Map;

/**
 * 数据访问接口 , 持久层接口
 */
public interface DeptMapper {
    /**
     * 查询
     * 当id= null, 查询全部
     * id < 10, 查询 id = 2的数据
     * id == 10, 查询 id = 3的数据
     * id > 10, 查询 id = 4的数据
     *
     * @param id
     * @return
     */
    @SelectProvider(type = MyInsertSqlProvider.class, method = "select")
    List<Dept> selectByProvider(Integer id);

    /**
     * 批量新增
     *
     * @param list
     * @return
     */
    @InsertProvider(type = MyInsertSqlProvider.class, method = "suibian")
    int insertBatch(List<Dept> list);

    @Results(value = { // {} 代表数组
            @Result(column = "dep_id", property = "depId"), @Result(column = "dep_name", property = "depName"), @Result(column = "dep_addr", property = "depAddr")})
    @Select("select dep_id, dep_name, dep_addr from tb_dept where dep_id = #{depId}")
    Dept selectByResults(Integer id);

    /**
     * 查询所有,返回结果每行封装一个Map集合。key是字段名,value是字段值
     *
     * @return
     */
    @Select("select dep_id , dep_name , dep_addr  from tb_dept")
    List<Map<String, Object>> selectAllAsMap();

    /**
     * 查询所有
     *
     * @return
     */
    @Select("select dep_id as depId, dep_name as depName, dep_addr as depAddr from tb_dept")
    List<Dept> selectAll();

    /**
     * 根据主键查询
     *
     * @param id
     * @return
     */
    @Select("select dep_id as depId, dep_name as depName, dep_addr as depAddr from tb_dept where dep_id = #{depId}")
    Dept selectById(Integer id);

    /**
     * 根据主键删除
     *
     * @param id
     * @return
     */
    @Delete("delete from tb_dept where dep_id = #{depId}")
    int deleteById(Integer id);

    /**
     * 根据主键更新其他字段
     *
     * @param dept
     * @return
     */
    @Update("update tb_dept set dep_name = #{depName}, dep_addr = #{depAddr} where dep_id = #{depId}")
    int updateById(Dept dept);

    /**
     * 新增
     *
     * @param dept
     * @return
     */
    @Insert("insert into tb_dept(dep_id, dep_name, dep_addr) values(#{depId}, #{depName}, #{depAddr})")
    @SelectKey(statement = "select @@identity as id", keyProperty = "depId", before = false, resultType = Integer.class, keyColumn = "id")
    int insertDept(Dept dept);
}
package com.bjsxt.provider;

import com.bjsxt.pojo.Dept;
import org.apache.ibatis.jdbc.SQL;

import java.util.List;

/**
 * 自定义的新增SQL语句处理类型
 */
public class MyInsertSqlProvider {
    /**
     * 动态生成SQL
     * MyBatis提供了一个类型,可以通过调用方法的方式,逐渐拼接一个完整的SQL。
     *
     * @param id
     * @return
     */
    public String select(Integer id) {
        // MyBatis提供的SQL生成类型
        SQL s = new SQL();
        // 拼接select语句,参数是若干字段
        s.SELECT("dep_id as depId", "dep_name as depName", "dep_addr as depAddr").FROM("tb_dept");

        if (id == null) {
            return s.toString();
        }
        if (id < 10) {
            s.WHERE("dep_id = 2");
            return s.toString();
        }
        if (id == 10) {
            s.WHERE("dep_id = 3");
            return s.toString();
        }
        if (id > 10) {
            s.WHERE("dep_id = 4");
            return s.toString();
        }

        // 返回生成的SQL语句。
        return s.toString();
    }

    /**
     * 动态生成SQL的方法定义要求:
     * 1. 公开
     * 2. 名称任意定义
     * 3. 返回String
     * 4. 如果需要参数,接口方法如何定义,当前方法如何定义
     * 方法内部,直接拼接完整的SQL,如果参数可以通过#{}或${}直接处理,可以设置占位变量。如果不可以直接处理,则在当前方法中设置。
     */
    public String suibian(List<Dept> list) {
        StringBuilder sql = new StringBuilder("insert into tb_dept(dep_id, dep_name, dep_addr) values ");

        for (Dept dept : list) {
            sql.append("(").append(dept.getDepId()).append(", \"").append(dept.getDepName()).append("\", \"").append(dept.getDepAddr()).append("\"),");
        }

        // 删除最后一个逗号
        sql.deleteCharAt(sql.length() - 1);
        System.out.println("动态生成的SQL是:" + sql.toString());
        return sql.toString();
    }
}
package com.bjsxt.test;

import com.bjsxt.mapper.DeptMapper;
import com.bjsxt.pojo.Dept;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 测试注解
 */
public class TestAnnotation {
    // 定义一个实例变量。
    private DeptMapper deptMapper;
    private SqlSession session;

    /**
     * 执行测试方法前作什么
     */
    @Before
    public void before() {
        session = MyBatisUtils.getFactory().openSession();
        deptMapper = session.getMapper(DeptMapper.class);
        System.out.println("before方法运行,给SqlSession和DeptMapper赋值");
    }

    @Test
    public void testSelectProvider() {
        List<Dept> list = deptMapper.selectByProvider(null);
        System.out.println(list);

        list = deptMapper.selectByProvider(1);
        System.out.println(list);

        list = deptMapper.selectByProvider(10);
        System.out.println(list);

        list = deptMapper.selectByProvider(11);
        System.out.println(list);

    }

    @Test
    public void testInsertBatch() {
        List<Dept> list = new ArrayList<>();
        list.add(new Dept(null, "市场部", "赛蒂工业园"));
        list.add(new Dept(null, "策划部", "赛蒂工业园"));
        list.add(new Dept(null, "开发部", "赛蒂工业园"));

        int rows = deptMapper.insertBatch(list);
        System.out.println(rows);

        session.commit();
        session.close();
    }

    @Test
    public void testSelectResults() {
        Dept dept = deptMapper.selectByResults(2);
        System.out.println(dept);

        session.commit();
        session.close();
    }

    @Test
    public void testSelect() {
        Dept dept = deptMapper.selectById(1);
        System.out.println(dept);

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

        List<Dept> list = deptMapper.selectAll();
        list.forEach(d -> {
            System.out.println(d);
        });

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

        List<Map<String, Object>> maps = deptMapper.selectAllAsMap();
        maps.forEach(map -> {
            System.out.println(map);
        });

        session.commit();
        session.close();
    }

    @Test
    public void testDelete() {
        deptMapper.deleteById(5);
        session.commit();
        session.close();
    }

    @Test
    public void testUpdate() {
        Dept dept = new Dept(4, "总裁办", "赛蒂工业园");
        deptMapper.updateById(dept);
        session.commit();
        session.close();
    }

    @Test
    public void testInsert() {
        SqlSession session = MyBatisUtils.getFactory().openSession();
        DeptMapper deptMapper = session.getMapper(DeptMapper.class);

        Dept dept = new Dept(null, "测试新增注解", "赛蒂工业园");

        System.out.println("新增前:" + dept);

        int rows = deptMapper.insertDept(dept);

        System.out.println(rows);
        System.out.println("新增后:" + dept);

        session.commit();
        session.close();
    }
}

【19】多表查询

表格关系:
 1. 一对一
    1.1 唯一外键: A表格有字段id。 B表格有字段id和a_id,其中a_id是外键,引用A表格的id字段,且a_id字段约束是unique。常用
    1.2 共享主键: A表格有字段id。 B表格有字段id,其中B表格的id字段是主键,且是一个外键,引用A表格的id字段。不常用

 2. 一对多
    A表格有字段id。 B表格有字段id和a_id,其中a_id是外键,引用A表格的id字段

 3. 多对多
    A表格有字段id。B表格有字段id。 C表格有字段a_id和b_id,分别引用A表格id和B表格的id。

Java中类型的关系有:
 依赖:弱关系,有类型A和B,只要A中有B,就是依赖。A依赖B。
 聚合:中等强度的关系,有类型A和B,A中有B类型的或B泛型的实例变量。
 组合:强关系,有类型A和B,A中有B类型或B泛型的实例变量;B中有A类型的或A泛型的实例变量。且A和B不能独立存在。

MyBatis如何处理多表关联查询:
 当前类型引用的其他类型属性是非集合: 在resultMap标签中,增加子标签 <association> 来描述这个属性。
 当前类型引用的其他类型属性是集合: 在resultMap标签中,增加子标签 <collection> 来描述这个属性。

MyBatis实现多表关联数据查询的方式有:
 N+1次查询:如,A类型对应表格a,B类型对应表格b。A类型中有List<B>类型的属性。查询A的同时,要求查询对应的B集合。 1 是查询A, N 是若干次查询和A有关系的B
 1次查询:如,A类型对应表格a,B类型对应表格b。A类型中有List<B>类型的属性。查询A的同时,要求查询对应的B集合。 使用多表联合查询的方式,一次性查询A和B。

学习案例:
 1. 查询全部地址,同时查询地址对应的客户数据
    1.1 N+1次
    1.2 1次
 2. 查询全部客户,同时查询客户对应的地址集合
    2.1 N+1次
    2.2 1次
 3. 业务装配,就是单独查询每个表格的数据,使用代码,把数据直接的关系维护起来。
    如:先查询所有的客户,迭代分析,根据客户主键,查询每个客户的地址集合,维护关系。
create table tb_customer(
  id int(11) primary key auto_increment,
  name varchar(32) comment '姓名',
  username varchar(32) comment '登录名',
  password varchar(32) comment '登录密码'
) comment '客户表格';

create table tb_address(
  id int(11) primary key auto_increment,
  province varchar(32) comment '省',
  city varchar(32) comment '市',
  address varchar(255) comment '具体地址',
  customer_id int(11) references tb_customer(id)
);

insert into tb_customer(id, name, username, password) values
  (default, '张三', 'zhangsan', '123'),
  (default, '李四', 'lisi', '123'),
  (default, '王五', 'wangwu', '123');

insert into tb_address(id, province, city, address, customer_id) values
  (default, '北京', '北京', '西三旗', 1),
  (default, '北京', '北京', '西二旗', 1),
  (default, '北京', '北京', '西北旺', 1),
  (default, '北京', '北京', '经海路8号', 2),
  (default, '北京', '北京', '经海路9号', 2),
  (default, '北京', '北京', '经海路10号', 2),
  (default, '北京', '北京', '天安门东', 3),
  (default, '北京', '北京', '天安门西', 3),
  (default, '北京', '北京', '前门大街', 3);
<?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>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <!-- 定义别名 -->
    <typeAliases>
        <package name="com.bjsxt.pojo"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/CustomerMapper.xml"/>
        <mapper resource="mappers/AddressMapper.xml"/>
    </mappers>
</configuration>
package com.bjsxt.pojo;

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

/**
 * 客户实体
 */
public class Customer implements Serializable {
    private Integer id;
    private String name;
    private String username;
    private String password;
    private List<Address> addressList;

    public Customer() {
    }

    public Customer(Integer id, String name, String username, String password) {
        this.id = id;
        this.name = name;
        this.username = username;
        this.password = password;
    }

    public List<Address> getAddressList() {
        return addressList;
    }

    public void setAddressList(List<Address> addressList) {
        this.addressList = addressList;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Customer customer = (Customer) o;
        return Objects.equals(id, customer.id) &&
                Objects.equals(name, customer.name) &&
                Objects.equals(username, customer.username) &&
                Objects.equals(password, customer.password);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, username, password);
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
package com.bjsxt.pojo;

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

/**
 * 地址
 */
public class Address implements Serializable {
    private Integer id;
    private String province;
    private String city;
    private String address;
    private Customer customer;
    private Integer customerId; // 外键字段

    public Address() {
    }

    public Address(Integer id, String province, String city, String address) {
        this.id = id;
        this.province = province;
        this.city = city;
        this.address = address;
    }

    @Override
    public String toString() {
        return "Address{" +
                "id=" + id +
                ", province='" + province + '\'' +
                ", city='" + city + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public Integer getCustomerId() {
        return customerId;
    }

    public void setCustomerId(Integer customerId) {
        this.customerId = customerId;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address1 = (Address) o;
        return Objects.equals(id, address1.id) &&
                Objects.equals(province, address1.province) &&
                Objects.equals(city, address1.city) &&
                Objects.equals(address, address1.address) &&
                Objects.equals(customer, address1.customer);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, province, city, address, customer);
    }

    public Integer getId() {
        return id;
    }

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

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }
}
package com.bjsxt.mapper;

import com.bjsxt.pojo.Address;

import java.util.List;

/**
 * 地址数据访问接口
 */
public interface AddressMapper {
    List<Address> selectAll();

    List<Address> selectAll1();
}
package com.bjsxt.mapper;

import com.bjsxt.pojo.Customer;

import java.util.List;

/**
 * 客户数据访问接口
 */
public interface CustomerMapper {
    List<Customer> selectAll();

    List<Customer> selectAll1();
}
<?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">

<mapper namespace="com.bjsxt.mapper.AddressMapper">
    <!-- 执行处理流程:
         1. 执行SQL,查询所有需要的数据
         2. 按照resultMap中的id和result子标签,封装Address类型对象,并给普通属性赋值。
         3. 按照association中的id和result子标签,封装Customer类型对象,并给普通属性赋值
     -->
    <resultMap id="addrMap1" type="Address">
        <id column="id" property="id"/>
        <result column="province" property="province"/>
        <result property="city" column="city"/>
        <result column="address" property="address"/>
        <!-- 如果用于描述的非集合类型引用类型属性,且这个属性的值,是通过多表联合查询得到的结果
             配置时,不需要提供select和column属性,需要增加子标签,描述多表联合查询中的字段和当前属性对应类型中的属性的关系。
         -->
        <association property="customer" javaType="Customer">
            <id column="cusId" property="id"/>
            <result column="name" property="name"/>
            <result column="username" property="username"/>
            <result column="password" property="password"/>
        </association>
    </resultMap>

    <!-- 1次查询,是使用关联查询实现。也就是多表联合查询实现。
         一条SQL,查出需要的一切数据。
     -->
    <select id="selectAll1" resultMap="addrMap1">
        select addr.id, addr.province, addr.city, addr.address, cus.id as cusId, cus.name, cus.username, cus.password from
        tb_address addr left join tb_customer cus on addr.customer_id = cus.id
    </select>

    <!-- 执行selectAll对应的SQL,结果处理流程是:
         1. 按照resultMap中的id和result标签,依次为属性赋值。
         2. 按照association标签配置的内容,把当前SQL的查询结果字段值作为条件,调用select属性对应的SQL,
            把对应SQL的查询结果,赋值给association属性。
     -->
    <resultMap id="addrMap" type="Address">
        <id column="id" property="id"/>
        <result column="province" property="province"/>
        <result property="city" column="city"/>
        <result column="address" property="address"/>
        <!-- 配置非集合类型的引用类型属性 customer,属性的类型是 Customer
             property - 非集合类型引用类型属性名
             javaType - 非集合类型引用类型属性的具体类型。 可以使用包名.类名或者别名
             select - 这个属性的值,用哪一个SQL去查询,赋值规则是 namespace.select标签id
                      如果这个select标签的id,全局唯一,可以省略namespace。不推荐
             column - 当前的resultMap使用在哪一个select标签上,那么使用这个一个标签中的SQL查询结果的哪一个字段的值作为参数,
                      调用select属性对应的SQL。
                      如果select属性对应的SQL只需要一个参数,则直接写查询SQL中的字段名。
                      如果select属性对应的SQL需要多个参数(包括一个),则使用语法{占位变量名 = 查询SQL的字段名},类似JSON格式。
        -->
        <association property="customer" javaType="Customer"
                     select="com.bjsxt.mapper.CustomerMapper.selectById"
                     column="{id = customer_id}"></association>
    </resultMap>

    <select id="selectAll" resultMap="addrMap">
        select id, province, city, address, customer_id from tb_address
    </select>

    <!-- 根据客户外键查询地址集合 -->
    <select id="selectAddrByCustomer" resultType="Address">
        select id, province, city, address from tb_address where customer_id = #{customerId}
    </select>

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

<mapper namespace="com.bjsxt.mapper.CustomerMapper">
    <resultMap id="cusMap1" type="Customer">
        <id column="cusId" property="id"/>
        <result column="name" property="name"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <collection property="addressList" javaType="java.util.ArrayList"
                    ofType="Address">
            <id column="id" property="id" />
            <result column="province" property="province"/>
            <result column="city" property="city"/>
            <result column="address" property="address"/>
        </collection>
    </resultMap>

    <select id="selectAll1" resultMap="cusMap1">
        select cus.id as cusId, cus.name, cus.username, cus.password, addr.id, addr.province, addr.city, addr.address from
        tb_customer as cus left join tb_address as addr on cus.id = addr.customer_id
    </select>

    <!-- 定义结果映射 -->
    <resultMap id="cusMap" type="Customer">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <!-- 定义集合类型的属性
             property - 集合类型属性的名字
             javaType - 集合类型属性的具体类型,可以写接口类型,也可以写具体实现类型。类型必须和属性的类型兼容。
             ofType - 集合类型的泛型,要求必须和属性的实际泛型匹配兼容。
             select - 为属性赋值,需要执行的SQL。namespace.selectTagId
             column - 执行SQL的时候,要传递的参数。一个参数,直接写当前SQL的字段名。多个参数 {占位变量名 = 字段名}
					指定将父对象的 id 属性传递给子查询中的 customerId 参数
			 			- id 代表的是 tb_custome 表中查询出来的id 
						- customerId 代表的是 
 								上述 id 会作为参数传递给 selectAddrByCustomer 查询的 #{customerId} 参数,
								用于查询该客户对应的地址列表
         -->
        <collection property="addressList" javaType="java.util.List"
              ofType="Address" select="com.bjsxt.mapper.AddressMapper.selectAddrByCustomer"
              column="{customerId = id}"></collection>
    </resultMap>
    <select id="selectAll" resultMap="cusMap">
        select id, name, username, password from tb_customer
    </select>

    <select id="selectById" resultType="Customer">
        select id, name, username, password from tb_customer where id = #{id}
    </select>
</mapper>
package com.bjsxt.test;

import com.bjsxt.mapper.AddressMapper;
import com.bjsxt.mapper.CustomerMapper;
import com.bjsxt.pojo.Address;
import com.bjsxt.pojo.Customer;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestAssociation {
    private SqlSession session;
    private AddressMapper addressMapper;
    private CustomerMapper customerMapper;
    @Before
    public void before(){
        session = MyBatisUtils.getFactory().openSession();
        addressMapper = session.getMapper(AddressMapper.class);
        customerMapper = session.getMapper(CustomerMapper.class);
    }

    @Test
    public void testAssociation1(){
        List<Address> list = addressMapper.selectAll1();
        for (Address address : list){
            System.out.println(address);
            System.out.println(address.getCustomer());
            System.out.println("====================================================");
        }
    }

    @Test
    public void testAssociation(){
        List<Address> list = addressMapper.selectAll();
//        for(Address address : list){
//            System.out.println(address);
//            //System.out.println(address.getCustomer());
//            System.out.println("==========================================================");
//        }
    }
}
package com.bjsxt.test;

import com.bjsxt.mapper.CustomerMapper;
import com.bjsxt.pojo.Address;
import com.bjsxt.pojo.Customer;
import com.bjsxt.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

public class TestCollection {
    private SqlSession session;
    private CustomerMapper customerMapper;

    @Before
    public void before() {
        session = MyBatisUtils.getFactory().openSession();
        customerMapper = session.getMapper(CustomerMapper.class);
    }

    @Test
    public void testCollection1() {
        List<Customer> list = customerMapper.selectAll1();
        for (Customer customer : list) {
            System.out.println(customer);
            for (Address address : customer.getAddressList()) {
                System.out.println(address);
            }
            System.out.println("=================================================");
        }
    }

    @Test
    public void testCollection() {
        List<Customer> list = customerMapper.selectAll();
        for (Customer customer : list) {
            System.out.println(customer);
            for (Address address : customer.getAddressList()) {
                System.out.println(address);
            }
            System.out.println("==============================================================");
        }
    }
}

【20】延迟加载

延迟加载只能出现在多表联合查询的N+1方式中
表示当执行当前方法时,是否立即执行关联方法的SQL
全局配置从3.4.1版本开始需要在MyBatis置文件里面配置lazyLoadingEnabled=true即可在当前项目所有N+1的位置开启延迟加载
局部配置需要在collection或association标签中配置fetchType属性。fetchType可取值:lazy(延迟加载)和eager(立即加载)
当配置了fetchType属性后,全局settings的配置被覆盖,对于当前标签以fetchType属性值为准
    <!-- 全局配置 mybatis.cfg.xml-->
	<settings>
        <setting name="lazyLoadingEnabled" value="true"/>
    </settings>
<!-- 局部配置 xxxMapper.xml fetchType -->
<resultMap id="empMap2" type="Emp">
    <id column="e_id" property="id"/>
    <result column="e_name" property="name"/>
    <!-- 
     fetchType - 配置查询方案。是否延迟。 默认采用全局配置。当前是局部配置。都配置的时候,局部优先。
                 可选值 eager(非延迟) 和 lazy(延迟)。在collection标签中也有,含义相同。
    -->
    <association property="dept" 
                 javaType="Dept"
                 select="com.bjsxt.mapper.DeptMapper.selectById" 
                 column="e_d_id"
    			fetchType="lazy">
    </association>
</resultMap>
<select id="selectAllN1" resultMap="empMap2">
    select e_id,e_name,e_d_id from emp
</select>
属性名解释说明可取值默认值
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true | falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。true | falsefalse (在 3.4.1 及之前的版本中默认为 true)

【21】缓存

[1] 一级缓存
会话级缓存、线程级缓存,默认开启一级缓存
要求:必须使用同一个会话、执行同一条SQL、使用完全相同的参数
一级缓存中的数据保存流程:
	1. 执行SQL,查询数据
	2. 查询结果保存到一级缓存中

[2] 二级缓存
会话工厂缓存、进程级缓存。
要求:必须使用同一个会话工厂,执行同一条SQL,使用完全相同的参数,且在事务结束后,才能使用的缓存。实体必须实现接口Serializable
二级缓存可能保存在内存,也可能保存在文件中。MyBatis使用Object输出/输入流,实现文件和内存的缓存数据读写。
二级缓存中的数据保存流程:
	1. 执行SQL,查询数据
	2. 查询结果保存到一级缓存中 
	3. 提交/回滚/关闭会话,刷新一级缓存到二级缓存 
	4. 查询同样的SQL,且使用同样的参数,使用二级缓存

[3] 注意
开发时,只用一级缓存。不用二级缓存。由于二级缓存,相对性能低,安全差。
二级缓存是以 namespace 为单位的,不同 namespace 下的操作互不影响
查询数据顺序 二级-->一级--->数据库--->把数据保存到一级,当sqlsession关闭或者提交的时候,把数据刷入到二级缓存中

[4] 只读缓存(readonly=true)
配置的目的是优化查询性能,确保缓存不被写入,但它不会影响数据库的提交操作。数据库中的数据会按照提交操作进行实际的修改。
提交操作对数据库的数据进行实际更改,不管缓存是否配置为只读。
MyBatis 通过自动清除相关缓存来确保数据的一致性,使得提交操作后的数据在下次查询时能够得到正确的反映。
 <!-- 全局配置,开启二级缓存 -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
 <!-- 当前namespace中的所有查询开启二级缓存,前提是全局配置开启二级缓存。 -->
<mapper namespace="com.example.MyMapper">
    <!-- 
		格式:
		<cache type="" readOnly="" eviction="" flushInterval="" size="" blocking=""/>
 	-->
    <cache type="org.apache.ibatis.cache.decorators.Cache"/>
    
    <!-- 
		在加入Cache元素的前提下让个别select元素不使用缓存,可以使用useCache属性,设置为false
		flushCache控制当前sql执行一次后是否刷新缓存
 	-->
    <select id="findByEmpno" resultType="emp" useCache="true" flushCache="false">
</mapper>
/**
* 缓存中存储的JavaBean对象必须实现序列化接口
*/
public class Dept implements  Serializable { ... }
cache标签属性含义默认值
type自定义缓存类,要求实现org.apache.ibatis.cache.Cache接口null
readOnly配置的目的是优化查询性能,确保缓存不被写入,但它不会影响数据库的提交操作。数据库中的数据会按照提交操作进行实际的修改。提交操作对数据库的数据进行实际更改,不管缓存是否配置为只读。MyBatis 通过自动清除相关缓存来确保数据的一致性,使得提交操作后的数据在下次查询时能够得到正确的反映。是否只读true:给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。false:会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全。false
eviction缓存策略LRU(默认) – 最近最少使用:移除最长时间不被使用的对象。FIFO – 先进先出:按对象进入缓存的顺序来移除它们。SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。LRU
flushInterval刷新间隔,毫秒为单位。默认为null,也就是没有刷新间隔,只有执行update、insert、delete语句才会刷新null
size缓存对象个数1024
blocking是否使用阻塞性缓存BlockingCachetrue:在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,保证只有一个线程到数据库中查找指定key对应的数据false:不使用阻塞性缓存,性能更好false

【23】四大核心接口介绍及执行流程

【24】自定义插件

MyBatis中支持扩展插件,所有插件都必须实现org.apache.ibatis.plugin.Interceptor接口,可以对四大核心接口进行拦截
List<Emp> selectAllpage();
<resultMap id="empMap5" type="Emp">
    <id column="e_id" property="id"/>
    <result column="e_name" property="name"/>
</resultMap>
<select id="selectByEid" resultMap="empMap5">
    select e_id,e_name,e_d_id from emp 
</select>
public class MyPageHelper {
    protected static Integer pageStart;// 分页起始行
    protected static Integer pageSize;// 查询条数
    public static void startPage(int pageStartArg,int pageSizeArg){
        pageStart = pageStartArg;
        pageSize = pageSizeArg;
    }
}
package com.bjsxt.interceptor;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.util.Properties;

// 必须有的注解
/**
* @Intercepts 表示当前是一个拦截器。
* @Signature 表示签名。
* 	type:拦截器主要拦截的类型.可以是四大核心接口。
* 	method:拦截type中的哪个方法
* 	args:method对应方法的参数。这个很重要,因为Java支持方法重载,不设置参数可能无法精确到具体的方法
*/
@Intercepts(value = {@Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = {Connection.class,Integer.class}
)})
public class MyPageHelperInterceptor implements Interceptor {
    // 这个方法的作用:实现拦截业务
    // 对于自定义分页插件来说,这个方法的作用就是在后面拼接limit x,y
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取拦截的对象
        StatementHandler target = (StatementHandler) invocation.getTarget();
        // 获取SQL绑定器
        BoundSql boundSql = target.getBoundSql();
        // 获取SQL语句
        String sql = boundSql.getSql();
        // 判断是否已经设置了分页条件
        if(MyPageHelper.pageStart!=null&&MyPageHelper.pageSize!=null) {
            // 注意limit前面空格
            sql += " limit " +MyPageHelper.pageStart+","+MyPageHelper.pageSize;
        }
        // 把修改后的SQL重新放回去
        MetaObject metaObject = SystemMetaObject.forObject(target);
        // 第一个参数为固定值,表示绑定的SQL
        metaObject.setValue("parameterHandler.boundSql.sql",sql);
        // 放行继续执行
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
//        System.out.println(target.getClass().getName()); 通过输出可以查询执行此方法时目标对象
        // 每次调用四大核心接口都会调用此方法,只需要对StatementHandler进行处理
        if(target instanceof StatementHandler){
            return Plugin.wrap(target,this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
        // 获取到后面配置插件时的属性,设定属性名为dialect(方言),这个属性是自定义的。
        System.out.println(properties.getProperty("dialect"));
    }
}
    <plugins>
        <plugin interceptor="com.bjsxt.interceptor.MyPageHelperInterceptor">
            <property name="dialect" value="mysql"/>
        </plugin>
    </plugins>
public class Test {
    public static void main(String[] args) throws IOException {
        InputStream is = Resources.getResourceAsStream("mybatis.cfg.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = factory.openSession();
        EmpMapper empMapper = session.getMapper(EmpMapper.class);
        // 设置分页条件代码必须放在调用SQL上面
        MyPageHelper.startPage(0,2);
        List<Emp> list = empMapper.selectAllpage();
        System.out.println(list);
        session.close();
    }
}

【25】执行器类型

【26】执行原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值