Bootstrap

MyBatis学习笔记(系统学习)

一、简介

1.MyBatis是什么

Mybatis的前身是Batis,他是一个持久层框架,或称为:ORM框架。

  • 总结:MyBatis就是用来访问数据库的!
  • 本质上对前面所学JDBC进行封装!

注意:所谓的框架就是别人已经写好的,对一些技术进行封装,比如:java框架一般会封装成为JAR;像其他的如JS,CSS等。而用了ORM框架,就不需要像之前操作JDBC数据库那样,一步一步进行编写,通过MyBatis可以快速的操作数据库!

2.持久层

DAO:Data Access Object 数据访问对象

用来对数据进行持久化操作,如将数据存入数据库、硬盘等,可以持久保存

3.ORM

Object Relation Mapping 对象关系映射,指的是java程序和数据库之间的映射关系

  • 类---->表
  • 对象—>一条数据或一条记录
  • 属性---->列

4.回顾JDBC

JDBC操作数据库的步骤:

  • Class.forName(DriverName)
  • DriverManager.getConnection(URL)
  • conn.prepareStatement()
  • ResultSet rs = ps.executeQuer()
  • while(rs.next()){} 通过这一步进行循环!
  • rs.close
  • statement.close
  • conneciton.close

在这数据库操作的一系列流程中,有些代码是不变的,有些代码是可变的

  • 数据库操作的可变部分:连接信息

DriverClassName,URL,user,password(也称这)

  • SQL语句
  • RM映射

二、正式使用MyBatis的准备工作

1. 需要在项目中添加JAR包

  • mymybatis-3.2.8.jar;版本话,3.0后面的版本都可以用!
  • mysql-connector-java;数据库驱动
  • log4j;日志包(可选)加入了这个包,可以看到日志的信息

这里的三个Jar包可以通过IDEA直接加载,也可以通过Maven或Gradle来进行加载

2. 相关的配置文件的准备工作

mybatis配置文件使用的是.xml文件,可以分为两大类:

  • config文件

主配置文件 ,在一个mybatis工程中有且只有一个,他是用来配置与整个工程配置相关的信息,如环境配置、插件配置、注册mapper文件等!

  • mapper文件

映射配置文件,在一个mybatis工程中可以有多个mapper文件,每一个mapper文件相当于原来的Dao的实现类。用来配置Dao功能的相关的SQL操作,如SQL语然啦,CRUD ,字段映射等!

注意:创建MyBatis的主配置文件时,如果一段代码需要重复利用的话,可以用模板来创建!

三、第一个MyBatis程序

1. 数据库的设计

在这里对一个数据库进行设计;

2. 创建主配置文件

核心配置文件,该文件一般位于src目录下,一般命名为:mybatis-config.xml(这是习惯问题,其他文件名也可以

  • mybatis中的xml约束条件:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  
</configuration>
  • mybatis-config.xml核心配置文件实例
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
       <!-- TODO:配置当前项目中可能用到的所有环境!
            default:默认使用的环境是哪个?值指向里面的某一个-->
           <environments default="hello">
               <!-- TODO:配置某一个具体数据库的环境,环境可以有多个!
                id:该环境的标识符!-->
               <environment id="hello">
                   <!-- TODO:事务管理器,他的作用是配置事务管理器
                    type:事务管理器的类型,取值有两种:jdbc( 简单的jdbc事务操作),JDBC默认是关闭提交事务的!conn.setAutoCommit(false)
                    managed:将事务交给其他的框架或容器操作,如Spring-->
                   <transactionManager type="jdbc"></transactionManager>
                   <!-- TODO:配置数据库的连接信息
                    type:配置数据源。他有三个类型
                       UNPOOLED:使用的简单的JDBC配置,没有使用连接池技术。只有简单的连接配置
                       POOLED:使用了连接池技术
                       JNDI:通过外部容器来获取连接-->
                   <dataSource type="POOLED">
                       <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                       <property name="url" value="jdbc:mysql://localhost:3306/newdb?useSSL=false&amp;serverTimezone=UTC"/>
                       <property name="username" value="root"/>
                       <property name="password" value="123456"/>
                   </dataSource>
               </environment>
           </environments>
</configuration>
  • SqlSession(持久化管理器),他是Mybatis的核心,他的创建方法是由工厂来创建的,即:SqlSessionFactoryBuilder,具体代码示例如下:
@Bean
public Connection connection(){
    // TODO: 2021/7/26 创建一个工厂
    SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    InputStream resourceAsStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory build = builder.build(resourceAsStream);
    // TODO: 2021/7/26 创建了持久化管理器 ,所谓工厂模式,是将配置信息放入工厂,最终将创建一个持久化管理器
    SqlSession sqlSession = build.openSession();
    Connection connection = sqlSession.getConnection();
    return connection;
}

3.创建映射文件

在数据库中有一个表,那么根据ORM,那么在JAVA中就会有一个对应的类,因此,在创建映射文件的时候,首先得在Java中创建一个类;

  • 创建对应数据库表的对类

用来配置DAO的SQL语句,每个数据库表和对应的类之间都有一个映射文件(xml文件)

  • 创建xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--如果采取的配置文件 + 接口的方式,那么namespace必须是接口的类全名-->
<mapper namespace="abc">
    <!-- TODO:
     insert:表示执行添加方法!
     id:表示执行添加操作的方法名,该方法名类似类中的方法名,是一个命名标识
        如果是纯配置文件,没接口,方法名可以随便写,但必须唯一;
        如果配置文件+接口的形式,id方法名的值必须与接口中一致!
		这里所说的接口:是指
     parameterType:表示方法的参数类型
                   如果是参数对象,可以使用类的全名
                   如果参数是变通的数据,可以使用mybatis中的别名
       标签体:使用缩写的SQL语句!
              使用#{XXX}表示点位符
              如果参数是对象,那位占位符XXX用自定义对象中的属性名
              如果参数是普通数据类型,那么占位符XXX名也就是参数名,用的是参数名这个标识指向的数据-->
    <insert id="insertUser" parameterType="com.dream.seeker.entity.impl.GradleUser">
        insert into customer(name,salary) values (#{name},#{salary})
    </insert>
</mapper>

我们自定义一个映射文件,映射文件名可以自定义,那么如何告诉应用程序我们定义的映射文件呢?

  • 注册映射文件

Mybatis中的主配置文件m中有一个和environments平行的标签mapper。示例如下:

<mappers>
    <!-- TODO:在主配置文件中,注册一个映射文件,可以有多个
              resource:映射文件的路径,写的是src中的路径-->
    <mapper resource="mapper/mybatis-mapper.xml"/>
</mappers>
  • 测试映射文件

映射文件是和主配置文件一体的文件,自定义好的映射文件(Mapper)需要在主配置文件中进行注册,映射文件有SQL语句,每个SQL语句都有类似于方法名一样的标识,我们在MyBatis中也可以称之为方法名;他还存在方法参数,该参数是Java中的一个数据对象或者普通的数据,这些对象中的属性值和普通数据就是要传入到数据库中的内容。

@Test
public void insertDate() {
    gradleUser.setName("hellojava");
    gradleUser.setSalary(3000);
    // TODO: 2021/7/27:方式一:纯配置文件,没有接口,直接读取Mapper文件中的SQL语句!
    sqlSession.insert("abc.insertUser",gradleUser);
    // TODO: 2021/7/27 由于在主配置文件中,自动事务提交管理是关闭的,因此,需要手动对事务进行提交 
    sqlSession.commit();
}

4.MyBatis中数据库日志:log4j

在MyBatis中,如果需要对数据库的操作进行日志输出,则需要加载数据库日志输出依赖,并配置属性文件,具体代码如下:

  • 添加log4j依赖文件
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  • 配置log4j属性文件

log4j属性文件必须在类路径src下,且文件名必须命名为:log4j.properties,属性文件的具体内容可以设置如下

log4j.rootLogger=INFO,console,dailyFile
# TODO 发布到阿里云记得添加,另外控制台不输出(只输出warn或者error信息)

# log4j.logger.org.mybatis = INFO
log4j.logger.com.imooc.mapper=INFO

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.encoding=UTF-8
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%l] - [%p] %m%n

# 定期滚动日志文件,每天都会生成日志
log4j.appender.dailyFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.dailyFile.encoding=UTF-8
log4j.appender.dailyFile.Threshold=INFO
# TODO 本地日志地址,正式环境请务必切换为阿里云地址
log4j.appender.dailyFile.File=C:/logs/maven-ssm-alipay/log.log4j
log4j.appender.dailyFile.DatePattern='.'yyyy-MM-dd
log4j.appender.dailyFile.layout=org.apache.log4j.PatternLayout
log4j.appender.dailyFile.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%l] - [%p] %m%n
  • 详细log4j配置文件案例:
log4j.rootLogger=INFO,consoleAppender,logfile,MAIL
log4j.addivity.org.apache=true
#ConsoleAppender,控制台输出
#FileAppender,文件日志输出
#SMTPAppender,发邮件输出日志
#SocketAppender,Socket 日志
#NTEventLogAppender,Window NT 日志
#SyslogAppender,
#JMSAppender,
#AsyncAppender,
#NullAppender
#文件输出:RollingFileAppender
#log4j.rootLogger = INFO,logfile
log4j.appender.logfile = org.apache.log4j.RollingFileAppender
log4j.appender.logfile.Threshold = INFO
# 输出以上的 INFO 信息
log4j.appender.logfile.File = INFO_log.html
#保存 log 文件路径
Log4j 从入门到详解
10
log4j.appender.logfile.Append = true
# 默认为 true,添加到末尾,false 在每次启动时进行覆盖
log4j.appender.logfile.MaxFileSize = 1MB
# 一个 log 文件的大小,超过这个大小就又会生成 1 个日志 # KB ,MB,GB
log4j.appender.logfile.MaxBackupIndex = 3
# 最多保存 3 个文件备份
log4j.appender.logfile.layout = org.apache.log4j.HTMLLayout
# 输出文件的格式
log4j.appender.logfile.layout.LocationInfo = true
#是否显示类名和行数
log4j.appender.logfile.layout.Title
=title:\u63d0\u9192\u60a8\uff1a\u7cfb\u7edf\u53d1\u751f\u4e86\u4e25\u91cd\u9519\u8b
ef
#html 页面的 < title >
############################## SampleLayout ####################################
# log4j.appender.logfile.layout = org.apache.log4j.SampleLayout
############################## PatternLayout ###################################
# log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
# log4j.appender.logfile.layout.ConversionPattern =% d % p [ % c] - % m % n % d
############################## XMLLayout #######################################
# log4j.appender.logfile.layout = org.apache.log4j.XMLLayout
# log4j.appender.logfile.layout.LocationInfo = true #是否显示类名和行数
############################## TTCCLayout ######################################
# log4j.appender.logfile.layout = org.apache.log4j.TTCCLayout
# log4j.appender.logfile.layout.DateFormat = ISO8601
#NULL, RELATIVE, ABSOLUTE, DATE or ISO8601.
# log4j.appender.logfile.layout.TimeZoneID = GMT - 8 : 00
# log4j.appender.logfile.layout.CategoryPrefixing = false ##默认为 true 打印类别名
# log4j.appender.logfile.layout.ContextPrinting = false ##默认为 true 打印上下文信息
# log4j.appender.logfile.layout.ThreadPrinting = false ##默认为 true 打印线程名
# 打印信息如下:
#2007 - 09 - 13 14 : 45 : 39 , 765 [http - 8080 - 1 ] ERROR com.poxool.test.test -
error 成功关闭链接
###############################################################################
#每天文件的输出:DailyRollingFileAppender
#log4j.rootLogger = INFO,errorlogfile
log4j.appender.errorlogfile = org.apache.log4j.DailyRollingFileAppender
log4j.appender.errorlogfile.Threshold = ERROR
log4j.appender.errorlogfile.File = ../logs/ERROR_log
log4j.appender.errorlogfile.Append = true
#默认为 true,添加到末尾,false 在每次启动时进行覆盖
log4j.appender.errorlogfile.ImmediateFlush = true
#直接输出,不进行缓存
# ' . ' yyyy - MM: 每个月更新一个 log 日志
# ' . ' yyyy - ww: 每个星期更新一个 log 日志
# ' . ' yyyy - MM - dd: 每天更新一个 log 日志
# ' . ' yyyy - MM - dd - a: 每天的午夜和正午更新一个 log 日志
# ' . ' yyyy - MM - dd - HH: 每小时更新一个 log 日志
# ' . ' yyyy - MM - dd - HH - mm: 每分钟更新一个 log 日志
Log4j 从入门到详解
11
log4j.appender.errorlogfile.DatePattern = ' . ' yyyy - MM - dd ' .log '
#文件名称的格式
log4j.appender.errorlogfile.layout = org.apache.log4j.PatternLayout
log4j.appender.errorlogfile.layout.ConversionPattern =%d %p [ %c] - %m %n %d
#控制台输出:
#log4j.rootLogger = INFO,consoleAppender
log4j.appender.consoleAppender = org.apache.log4j.ConsoleAppender
log4j.appender.consoleAppender.Threshold = ERROR
log4j.appender.consoleAppender.layout = org.apache.log4j.PatternLayout
log4j.appender.consoleAppender.layout.ConversionPattern =%d %-5p %m %n
log4j.appender.consoleAppender.ImmediateFlush = true
# 直接输出,不进行缓存
log4j.appender.consoleAppender.Target = System.err
# 默认是 System.out 方式输出
#发送邮件:SMTPAppender
#log4j.rootLogger = INFO,MAIL
log4j.appender.MAIL = org.apache.log4j.net.SMTPAppender
log4j.appender.MAIL.Threshold = INFO
log4j.appender.MAIL.BufferSize = 10
log4j.appender.MAIL.From = [email protected]
log4j.appender.MAIL.SMTPHost = smtp.gmail.com
log4j.appender.MAIL.Subject = Log4J Message
log4j.appender.MAIL.To = [email protected]
log4j.appender.MAIL.layout = org.apache.log4j.PatternLayout
log4j.appender.MAIL.layout.ConversionPattern =%d - %c -%-4r [%t] %-5p %c %x - %m %n
#数据库:JDBCAppender
log4j.appender.DATABASE = org.apache.log4j.jdbc.JDBCAppender
log4j.appender.DATABASE.URL = jdbc:oracle:thin:@ 210.51 . 173.94 : 1521 :YDB
log4j.appender.DATABASE.driver = oracle.jdbc.driver.OracleDriver
log4j.appender.DATABASE.user = ydbuser
log4j.appender.DATABASE.password = ydbuser
log4j.appender.DATABASE.sql = INSERT INTO A1 (TITLE3) VALUES ( ' %d - %c %-5p %c %x - %m%n
' )
log4j.appender.DATABASE.layout = org.apache.log4j.PatternLayout
log4j.appender.DATABASE.layout.ConversionPattern =% d - % c -%- 4r [ % t] %- 5p % c %
x - % m % n
#数据库的链接会有问题,可以重写 org.apache.log4j.jdbc.JDBCAppender 的 getConnection() 使用数
据库链接池去得链接,可以避免 insert 一条就链接一次数据库

5.如何在IDEA中快速的使用数据库中的字段等信息来确定SQL语句

通过IDEA可以连接MySQL数据库,连接数据库后,在编写mapper文件的时候可以有提示信息,可以防止错误的发生。在IDEA连接数据库时,一定要注意设置serverTimezone=UTC,才能正确的连接

  • 连接数据库后,一般情况下也不会有提示信息,此时,需要对IDEA进行设置

File->Setting->Languages&Frameworks->SQL Dialects 将全局和项目的SQL Dialects都设置为:MySQL

四、基于接口的MyBatis映射文件

基于接口的映射文件,他和纯配置文件的主要区别在于:

  • 纯配置文件是通过持久性管理器sqlSession来调用映射文件中的SQL语句对数据库进行操作;
  • 而 接口 + 配置文件 的方式是通过接口生成的实现类(代理类)来执行接口中现有的方法来执行SQL语句,接口中的方法名 和 映射配置文件中的方法名必须一致,因此,通过调用接口实现类(由字节码操作工具类动态生成的代理对象)中的方法来驱动映射配置文件中的方法,从而执行SQL语句,达到操作数据库的目的!

具体案例代码:

  • 自定义一个操作数据库的接口
package com.dream.seeker.dao;

import com.dream.seeker.entity.impl.GradleUser;

public interface InsertUser {
    public void insertUser(GradleUser user);
}
  • 配置MyBatis映射配置文件
<?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.dream.seeker.dao.InsertUser">
    <!-- TODO:
     insert:表示执行添加方法!
     id:表示执行添加操作的方法名,该方法名类似人类中的方法名,是一个命名标识
        如果是纯配置文件,没接口,方法名可以随便写,但必须唯一;
        如果配置文件+接口,id方法名的值必须与接口中一致!
     parameterType:表示方法的参数类型
                   如果是参数对象,可以使用类的全名
                   如果参数是变通的数据,可以使用mybatis中的别名
       标签体:使用缩写的SQL语句!
              使用#{XXX}表示点位符
              如果参数是对象,那位占位符XXX用自定义对象中的属性名
              如果参数是普通数据类型,那么占位符XXX名也就是参数名,用的是参数名这个标识指向的数据-->
    <insert id="insertUser" parameterType="com.dream.seeker.entity.impl.GradleUser">
        insert into
            gradle(name, salary) values (#{name},#{salary})
    </insert>
</mapper>
  • 通过测试类来执行SQL语句
@Test
public void test97() {
    gradleUser.setName("hello");
    gradleUser.setSalary(200);
    // TODO: 2021/7/27 通过getMapper()方法,在方法中传入接口的结构信息,可以动态创建一个代理类对象 
    InsertUser mapper = sqlSession.getMapper(InsertUser.class);
    // TODO: 2021/7/27 通过动态生成的代理类对象调用其中方法来驱动映射配置文件中的SQL语句,或者可以说:SQL语句已经动态生成到接口实现类中的方法当中
    mapper.insertUser(gradleUser);
    // TODO: 2021/7/27 由于在MyBatis主配置文件中,事务管理属性JDBC,因此,需要手动提交事务
    sqlSession.commit();
}

知识点:通过接口 + 配置文件 的方式来操作数据库,他基本原理是:通过接口的结构信息 + 配置文件中与接口方法名相同的SQL语句,动态生成(由字节码操作工具)一个接口的实现类及实现类对象,通过这个实现类对象(代理类对象)调用对象中的方法来执行SQL语句,从而操作数据库。其主要优点是:接口动态生成的实例对象每一个方法都对应映射文件中的一个SQL语句!

五、MyBatis工具类

  • 下面的代码,如果每次都要写,那就比较麻烦。
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
    InputStream resourceAsStream = SqlSessionFactoryBuilder.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
    SqlSessionFactory build = builder.build(resourceAsStream);
    // TODO: 2021/7/26 创建了持久化管理器 ,所谓工厂模式,是将配置信息放入工厂,最终将创建一个持久化管理器
    SqlSession sqlSession = build.openSession();

因此,在这种情况下,我们可以创建一个工具类,返回一个持久化管理器SqlSession ,同时,需要注意的是:在工具类中,SqlSession对象必须是单例唯一的。具体示例代码如下:

public class GradleMyBatisUtils {

    private static SqlSessionFactory factory;
    // TODO: 2021/7/28 可以把ThreadLocal<>看成是一个容器,这个容器有判断hash值的方法,确定在里面的实例是否是唯一(即单例)
    private static ThreadLocal<SqlSession> local = new ThreadLocal<>();

    static {
        try {
            factory = new SqlSessionFactoryBuilder().build(
                GradleMyBatisUtils.class.getClassLoader().getResourceAsStream("classpath:mybatis-config.xml")
            );
        } catch (Exception e) {
            throw new ExceptionInInitializerError("MyBatis初始化失败" + e.getMessage());
        }
    }

    // TODO: 2021/7/28 获取一个持久化管理器,管理器里包数据库连接池,事务和数据库连接的相关信息,具体内容和配置文件中有关
    public static SqlSession getSession(){
        // TODO: 2021/7/28 从容器中获得对象, 由于是单例的,所以,会依据类型进行匹配
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = factory.openSession();
            local.set(sqlSession);
        }
        return sqlSession;
    }

    // TODO: 2021/7/28 关闭持久化管理对象,关闭此对象,会同时关闭数据库连接池中相关对象,如connection,statement等! 
    public static void close(){
        SqlSession sqlSession = local.get();
        if (sqlSession!=null) {
            sqlSession.close();
            local.remove();
        }
    }
}

新增知识点:在工具类中,我们可以用ThreadLocal类实例来管理工具类中对象是否为单例,ThreadLocal实例具有存储和管理工具类中指定对象类型的功能,可以用来判断工具类中主对象是否为空,如果不为空的话,可以通过get()方法直接获得相应的对象。

  • 对MyBatis工具类进行测试
@Test
public void test110() {
    SqlSession sqlSession = null;
    try {
        // TODO: 2021/7/28 从工具类中获得持欠化管理器 
        sqlSession = MyBatisUtils.getSession();

        // TODO: 2021/7/28 下面几段代码是具体对数据库进行操作的代码  
        GradleUser gradleUser = new GradleUser();
        gradleUser.setName("hellojava");
        gradleUser.setSalary(5000);
        InsertUser mapper = sqlSession.getMapper(InsertUser.class);
        mapper.insertUser(gradleUser);

        // TODO: 2021/7/28 由于事务采取的方式是JDBC方式,他自动提交是关闭的,因此,需要手动关闭 
        sqlSession.commit();
    } catch (Exception e) {
        e.printStackTrace();
        // TODO: 2021/7/28 如果发生异常,事务没有执行完,地么需要回滚操作 
        sqlSession.rollback();
    } finally {
        MyBatisUtils.close();
    }
}

六、MyBatis主配置文件的扩展:配置别名,引用属性文件

  • typeAliases给自定义数据信息类设置别名

在主配置文件中,添加标签选项:typeAliases,可以为java类对象设置别名(这个JAVA类对象是存储数据库信息的Bean,使用此类对象设置数据,再将此类对象中的数据传入到数据库),这个java类对象是执行SQL方法中的参数,这个参数他位于MyBatis映射文件中。设置别名主要是在MyBatis主配置文件中,使用标签typeAliases,具体示例如下:

 <!-- TODO:设置别名 -->
<typeAliases>
    <!-- TODO:为某一个类去配置别名 -->
    <!--        <typeAlias type="com.dream.seeker.entity.impl.GradleUser" alias="GradleUser"/>-->
    <!-- TODO:package:为包下面的所有类取别名
                  name:指定包名,该包下所有类的别名,就是其自身的类名,原则上不区分大小上,但建设采用首字母大写-->
    <package name="com.dream.seeker.entity.impl"/>
</typeAliases>

设置好别名后,那么就可以在MyBatis映射文件中使用别名了,下面的代码是基于接口 + 配置文件的方式定义的mapper(映射)文件,其中namesapce是接口名,id指的是接口的抽象方法名,而parameterType这个地方通过在主配置文件中定义的别名,可以简化许多代码。

<?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.dream.seeker.dao.InsertUser">
    <!-- TODO:
     insert:表示执行添加方法!
     id:表示执行添加操作的方法名,该方法名类似人类中的方法名,是一个命名标识
        如果是纯配置文件,没接口,方法名可以随便写,但必须唯一;
        如果配置文件+接口,id方法名的值必须与接口中一致!
     parameterType:表示方法的参数类型
                   如果是参数对象,可以使用类的全名
                   如果参数是变通的数据,可以使用mybatis中的别名
       标签体:使用缩写的SQL语句!
              使用#{XXX}表示点位符
              如果参数是对象,那位占位符XXX用自定义对象中的属性名
              如果参数是普通数据类型,那么占位符XXX名也就是参数名,用的是参数名这个标识指向的数据-->
    <insert id="insertUser" parameterType="GradleUser">
        insert into
        gradle(name, salary) values (#{name},#{salary})
    </insert>
</mapper>
  • 在属性文件中写数据库的连接信息:

在属性文件中写连接信息,首先得定义一个属性文件xxxx.properties,将相关的连接信息写入到里面,案例如下:

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/newdb?serverTimezone=UTC&useSSL=false
jdbc.username=root
jdbc.password=123456

在定义完属性文件后,需要在MyBatis的主配置文件中将自定义的属性文件添加到主配置文件中,在主配置文件中,用到了标签:properties,具体代码案例如下:

<properties resource="dbcp.properties"/>

在MyBatis主配置文件中定义好了属性文件后,则可以改写主配置文件中的连接信息,改写的内容采用 表 达 式 , 连 接 信 息 也 是 通 过 属 性 后 置 处 理 器 的 结 {}表达式,连接信息也是通过属性后置处理器的结 {}表达式进行转换,具体代码案例如下:

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

七、MyBatis对数据库表的基本操作

1.在插入操作中返回主键值

基于接口 + 配置文件 的方式对数据库进行插入操作SQL时,一般情况下是不返回主键的,如果需要返回主键,那么需要在mapper文件中添加属性:userGeneratedKey。此属性的作用是保留进行插入SQL操作时的主键,此主键的值保存在哪个位置呢?答案是:保存到方法参数对象的属性中,然而,自定义的参数对象有许多属性,他又会保存到哪个属性当中呢,这时,在mapper文件中,又需要用到一个属性名为🔑KeyProertyr的属性,此属性指的是自定义参数对象中哪个属性。具体代码案例流程如下:

  • 自定义数据Bean的结构信息
@Component(value = "gradleuser")
public class GradleUser {

    private int id;
    private String name;
    private long salary;

    ...
}
  • 在接口中添加新的方法
public interface InsertUser {
    public void insertUser(GradleUser user);
    public void insertGradle(GradleUser user);
}
  • 在映射文件(mapper)中 设置能返回主键的相关参数
<insert id="insertGradle" parameterType="GradleUser" useGeneratedKeys="true" keyProperty="id">
    insert into gradle(name, salary) VALUES (#{name},#{salary})
</insert>
  • 通过测试方法来对
@Test
public void test130() {
    SqlSession sqlSession = null;
    try {
        sqlSession = MyBatisUtils.getSession();

        GradleUser gradleUser = new GradleUser();
        gradleUser.setName("thisisjava");
        gradleUser.setSalary(3500);

        InsertUser mapper = sqlSession.getMapper(InsertUser.class);
        mapper.insertGradle(gradleUser);
        System.out.println(gradleUser.getId());

        sqlSession.commit();
    } catch (Exception e) {
        e.printStackTrace();
        sqlSession.rollback();
    } finally {
        MyBatisUtils.close();
    }
}

2.更新操作

在这时统一学习的是基于接口+ 配置文件 的方式

  • 在接口中定义一个更新操作的方法
  • 在映射文件中增加更新操作的标签update

具体代码示例如下:

  • 在接口进定义更新方法
public interface BatisOne {
   void insertStudent(Student student);
   void updataStudent(Student student);
   void deleteStudent(int id);
}
  • 在映射文件中定义更新操作的标签,并配置相关信息
<update id="updataStudent" parameterType="Student">
    <!-- 此处所有SQL语句中的点位符的值都来自于对象参数中的属性 -->
    update student set name=#{name},age = ${age} where id=#{id}
</update>
  • 测试
@Test
public void test46() {
    Student student = new Student(1, "hellojava", 30);
    batisOne.updataStudent(student);
}

3.删除操作

通过Mybatis以数据库表中的数据进行删除时,参数中可以不指定对象,可以直接指定数据库表中的一个数据类型做为参数,也就是我们常说的基本数据类型,如下面,Integer类型做为参数:

public interface BatisOne {
   void insertStudent(Student student);
   void updataStudent(Student student);
   void deleteStudent(int id);
}
<delete id="deleteStudent" parameterType="Integer">
    <!--此处的id,也就是接口方法中形参的id -->
    delete from student where id = #{id}  
</delete>

基本数据类型做为参数时,在映身文件中的SQL语句中,#{XXX}中可以直接用接口方法 中定义的形参标识符来填充XXX点位符。

  • 测试:
@Test
public void test52() {
    batisOne.deleteStudent(1); //这个地方直接传入基本数据类型
}

4.查询操作

在mybatis映射文件中有一个标签为:select,可以对数据库中的数据进行查询操作

<--前提条件:查询结果的字段名必须和对象的属性名相同 -->
<select id="selectStudent" parameterType="Integer" resultType="Student">
    select id,name,age from student where id=#{id}
</select>
  • 返回的是一个对象,会自动进行映射,当然,查询后的结果集字段名必须和对象中的属性名相相同

  • 对于返回值为一个集合时,这个集合是一个容纳Student对象的集合,那么在映射文件的参数也可以是Student对象,因为Mybatis帮我们封装了许多技术,可以逐条的将数据库表中的每条记录映射到Student对象中,再将对象放入集合中。

具体代码示例如下:

  • 在接口中定义方法,接口中的所有方法必须和映射文件中的SQL操作相对应
public interface BatisOne {
   void insertStudent(Student student);
   void updataStudent(Student student);
   void deleteStudent(int id);
   Student selectStudent(int id);
   List<Student> selectAll();
}
  • 在映射文件中创建与接口中方法相对应的SQL语句标签
<select id="selectStudent" parameterType="Integer" resultType="Student">
    select id,name,age from student where id=#{id}
</select>
<select id="selectAll" resultType="Student">
    select <include refid="studentFiled"></include> from student
</select>
  • 补充知识点:如果一个数据库中的字段很多,每次在映射文件中写SQL语句时,就会比较繁琐,因此,MyBatis在映身文件中定义了SQL语句片断,定义好的片断可以在SQL语句中进行引用,具体代码示例如下:
<!-- TODO:定义sql代码段,免去后面重复写字段的名字 -->
<sql id="studentFiled">
    id,name,age
</sql>
<!-- TODO:用include标识符来调用sql片标,refid为指定哪个字段集 -->
<select id="selectAll" resultType="Student">
    select <include refid="studentFiled"></include> from student
</select>

整体映射文件代码如下:包含了更新,删除、查询的所有语句

<?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="dream.dao.batis.BatisOne">

    <!-- TODO:定义sql代码段,免去后面重复写字段的名字 -->
    <sql id="studentFiled">
        id,name,age
    </sql>
    <insert id="insertStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="id">
        insert into student(name, age) values (#{name},#{age})
    </insert>
    <update id="updataStudent" parameterType="Student">
        update student set name=#{name},age = ${age} where id=#{id}
    </update>
    <delete id="deleteStudent" parameterType="Integer">
        delete from student where id = #{id}
    </delete>
    <select id="selectStudent" parameterType="Integer" resultType="Student">
        select id,name,age from student where id=#{id}
    </select>
    <!-- TODO:用include标识符来调用sql片标,refid为指定哪个字段集 -->
    <select id="selectAll" resultType="Student">
        select <include refid="studentFiled"></include> from student
    </select>

</mapper>

5.手动映射

  • 自动映射:指的是查询结果集的字段名和自定义对象的属性名相同

  • 手动映射:在查询结果集和自定义对象属性名不相同

如何进行手动映射:

在学习手动映射之前,我们需要定义一个自定义对象,这个对象的属性和数据库表中的字段名不一样,如下:

public class NewStudent {

    private int user_id;
    private String user_name;
    private int user_age;
    ...
}
  • 第一种方式:使用别名,也就是将查询结果集的字段都设置成别名,别名的名字必须和自定义对象的属性名字相同。(这种方式一般不常使用)
<select id="selectNewStudent" parameterType="Integer" resultType="NewStudent">
    select id user_id, name user_name, age user_age
    from student
    where id = #{id}
</select>
  • 第二种方式:使用resultMap:resultMap用于在映射文件中标签中,是用来定义结果的映射,作用是将数据库的字段与对象的属性进行对应的映射,在标签中使用resultMap时,需要在外部定义一个映射标签:
    <!-- TODO:外部定义的映射标签
        1:id:是一个标识符,在后面sql语句中被调用
        2:type:要映射到的返回对象
        3.区分主键和普通字段,主键单独用id标签,普通字段用result标签
        4.proerties是自定义对象中的属性,column是数据库表中的字段-->
    <resultMap id="studentMapper" type="NewStudent">
        <id property="user_id" column="id"/>
        <result property="user_name" column="name"/>
        <result property="user_age" column="age"/>
    </resultMap>

定义好resultMap,就可以在映射文件中的SQL语句中使用定义的resultMap了

<select id="selectTwoStudent" parameterType="Integer" resultMap="studentMapper">
    select id, name, age
    from student
    where id = #{id}
</select>

6.多参数查询

基于接口和映射配置文件定义的Mybatis中,如果接口定义中的方法定义了两个参数,应该如何处理呢?

  • 重点:学习这点,可以认为把映射文件中的SQL语句都移到了接口的实现类中,这样,更容易理解 SQL语句对形参的调用。

  • 有多个参数,那么在SQL中可以就不写了,在映射文件的SQL不写参数还可以用于Map集合做为接口方法中的参数。

**第一种方式:**在接口中定义的参数,实际上有索引的,第一个参数为0,第二个参数为1。通过实验,这种情况一般用不了,可以在sql语句的点位符中直接引有形参名。

  • 定义接口中的方法和参数
// TODO: 2021/8/11 这里使用索引的方式,一般情况下,索引无效,可以直接使用参数名放在占位符中
ClassStudent selectStudent(String dname, int age);
  • 在映射文件中实现与接口方法对应的SQL语句
<!-- TODO:使用索引,也可以直接使用接口方法中的参数名,多参,无需在语句中定义参数类型parameterType -->
<select id="selectStudent" resultType="ClassStudent">
    select name,age,address from dbuser where name=#{dname} and age=#{age}
</select>

**第二种方式:**使用@Param注解,他是标注在接口方法的参数前,注解中定义的名称,也是在映射文件的SQL语句点位符所要用到的名称。

  • 定义接口中的方法和参数
// TODO: 2021/8/11 这里使用注解的方式 ,由于是多参,所以在SQL映射文件中无需定义参数
ClassStudent selectTwoStudent(@Param(value = "helloname") String username, @Param(value = "helloage") int userage);
  • 在映射文件中实现与接口方法对应的SQL语句
<!-- TODO: 使用@Param注解,点位符中使用的是注解中定义的名字-->
<select id="selectTwoStudent" resultType="ClassStudent">
    select name,age,address from dbuser where name=#{helloname} and age=#{helloage}
</select>

**第三种方式:**可以把多个参数变成一个参数,也就是参数封装成一个对象。一般常用的方法,传递进去的对象和返回的对象是同一个对象。如果要传递进去的参数在已知的定义的对象中没有相应的属性,那么可以再自定义一个封装类。

  • 定义接口中的方法和参数
// TODO: 2021/8/11 通过自定义对象,如果自定义对象中有能传递给语句的属性,而且返值也能放到这个对象中,那么参数和返回值可以是同一个类
ClassStudent selectClassStudent(ClassStudent student);
  • 在映射文件中实现与接口方法对应的SQL语句
<!-- TODO:自定义对象传递,需要定parameterType,参数和返回值可以是同一个对象,如果对象中的属性够用的话 -->
<select id="selectClassStudent" parameterType="ClassStudent" resultType="ClassStudent">
    select name,age,address from dbuser where age=#{age}
</select>

**第四种方式:**直接将参数封装成一个Map集合。在#{}占位符中,可以直接使用Map集合中Key的值。

  • 定义接口中的方法和参数
// TODO: 2021/8/11 在映射文件中的SQL语句中,无需定义参数,点位符使用Map中的Key值
ClassStudent selectMapStudent(Map map);
  • 在映射文件中实现与接口方法对应的SQL语句
<!-- TODO:使用Map集合,点位符使用的是Map中的Key对应的值 -->
<select id="selectMapStudent" resultType="ClassStudent">
    select name,age,address from dbuser where age=#{age}
</select>

**总结分析:**在一般情况下,使用注解,和自下定义对象的方法比较居多,具体情况还得根据情况而定。

7.模糊查询

模糊查询主要有二种:一种是手动模糊查询;另一种是使用Bind进行查询

  • 手动模糊查询

使用手动模糊查询,模糊查询的匹配符是在外面进行定义的,映射文件里厕所SQL语句接收的参数仍是接口中定义的方法形参数标识名

  1. 在接口中定义方法,方法参数为要查询的内容
   List<Student> selectOut(String s);
  1. 在映入文件中定义用于模糊查询的SQL语句
<!-- 在映射文件中的SQL语句中,只要不是不是自定义对象做为参数,都可以在语句中不写ParameterType -->
<select id="selectOut" resultType="Student"> 
    select name,age from student where name like #{s}
</select>
  1. 测试
@Test
public void test83() {
    String name = "a";
    List<Student> students = batisOne.selectOut("%"+name+ "%");
    for (Student student : students) {
        System.out.println(student);
    }
}
  • 使用Bind进行模糊查询

使用Bind进行模糊查询,也可以理解成为内置模糊查询,在方法外部不定义匹配标识符,而是在方法里面进行定义

  1. 在接口中定义方法,方法参数为要查询的内容
   List<Student> selectIn(String s);
  1. 在映入文件中定义用于模糊查询的SQL语句
<select id="selectIn" resultType="Student">
    select name,age from student
    <bind name="abc" value="'%'+_paramater+'%'"/>
    where name like #{abc}
</select>
  1. 测试
@Test
public void test94() {
    String name = "c";
    List<Student> students = batisOne.selectIn(name);
    for (Student student : students) {
        System.out.println(student);
    }
}

八、动态SQL

根据条件的不同,动态的拼接SQL语句,称之为动态SQL。

动态SQL都是在在映射文件中利用相对应的标签进行定义的,其主要目的就是给指定要查询的语句进动态的拼接

1.动态SQL之if标签

SQL的if语句,其主用途是对传入的参数对象的某个属性进条件判断,如果条件为真,就可以执行或者拼接SQL语句。

  • 判断是在if标签中的test属性中进行的。

示例:对一个对象参数的属性进行条件判断,如果符合相应的条件,则进行SQL语句的拼接。

<select id="selectif" parameterType="Student" resultType="Student">
    select name, age
    from student
    where 1 = 1
    <if test="name!=null and name!=''">
        and name = #{name}
    </if>
</select>

注意点:判断条件中的属性都来源于方法中的参数,如果接口方法参数是一个自定义对象,那么就来源于这个自定义对象的属性。

2.动态SQL之choose标签

在映射文件中的SQL语句中,还有一个标签choose,用于条件的选择,其主要意思是:只会拼接一个SQL语句。

  • choose标签中,里面有一个子标签when,字标签配合test主要用于判断条件是否成立。
<select id="selectchoose" parameterType="Student" resultType="Student">
    select name, age
    from student
    where
    <choose>
        <when test="name != null and name !=''">
            name=#{name}
        </when>
        <when test="age!=null and age!=0">
            age=#{age}
        </when>
        <otherwise>
            1=1
        </otherwise>
    </choose>
</select>

3.动态SQL之where标签

where标签:一般要结合if或choose一起使用,主要有两个作用:

  • 添加where关键字
  • 删除sql片段的第一个关键字,如and 或 or
  • 如果没有拼接任何sql片段,则不会添加where关键字
<select id="selectwhere" parameterType="Student" resultType="Student">
    select name,age from student
    <where>
        <if test="name != null and name != ''">
            and name=#{name}
        </if>
    </where>
</select>

4.动态SQL之set标签

更新,修改的时候用set标签,set标签不能单独使用,一般也需要结合if和choose来使用。具体代码示例如下:

<update id="updateset" parameterType="Student">
    update student
    <set>
        <if test="name !=null and name !=''">
            name = #{name},
        </if>
        <if test="age!=null and age!=0">
            age=#{age}
        </if>
    </set>
    <where>
        <if test="id!=null and id!=''">
            and id=#{id}
        </if>
    </where>
</update>

5.动态SQL之trim标签

trim标签的作用,可以在语句的两边去除、增加相关的内容,其主要作用如下:

  • 可以在语句的开头、或者是末尾添加特定的前缀或后缀
  • 可以删除开头或末尾参数指定的内容
<select id="selecttrim" parameterType="Student" resultType="Student">
    select name,age from student
    <trim prefix="where" prefixOverrides="and|or">
        <if test="id != null">
            and id=#{id}
        </if>
    </trim>
</select>

6.动态SQL之foreach标签

增强for循环,如在数据库表中,需要同时找出字段不同值的内容。foreach标签中有多个属性:

  • collection属性:要遍历的集合,主要意思是:所有的集合都会转换成一个Map,Map的key名是传入参数的类型。
  • item属性:迭代变量
  • open属性:遍历前添加的字符串
  • close属性:遍历后添加的字符串
  • spearator:元素的分隔符
  • index:遍历元素的索引

其主要代码示例如下:

  1. 在接口中定义方法
List<Student> selectforeach(List<Integer> list);
  1. 在映射文件中定义关于foreach标签的语句
<select id="selectforeach" resultType="Student">
    select name,age from student
    where id in
    <foreach collection="list" item="abc" open="(" close=")" separator=",">
        #{abc}
    </foreach>
</select>
  1. 测试
@Test
public void test153() {
    List<Integer> integers = Arrays.asList(3, 4);
    List<dream.entity.impl.Student> selectforeach = batisOne.selectforeach(integers);
    for (dream.entity.impl.Student student : selectforeach) {
        System.out.println(student);
    }
}

九、多表关系之间的映射

1.多表关系映射

多表映射关系核心思想

  • 每一个数据库表、数据库对象、接口、映射文件都是一一对应的关系
  • 在ORM中,一个数据库对象中的一个属性可以是另外一个数据库表对象
  • 当对象模型做为SQL语句的一个参数的时候,如果有一个属性是对象,那么在点位符#{}中可以用这个对象的属性。

具体代码录例如下:

映入文件中的SQL语句

<insert id="insertEmp" parameterType="Emp">
    insert into emp(empname, empage, deptid)
    VALUES (#{empname}, #{empage}, #{dept.deptid})
</insert>

测试

@Test
public void test237() {
    Dept dept = new Dept();
    Dept dept1 = new Dept();
    dept.setDeptname("市场部");
    dept1.setDeptname("营销部");

    Emp emp1 = new Emp("admin", 20, dept);
    Emp emp2 = new Emp("javachello", 30, dept1);

    try {
        dreamDept.insertDept(dept);
        dreamDept.insertDept(dept1);

        dreamEmp.insertEmp(emp1);
        dreamEmp.insertEmp(emp2);

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

通过以上两段代码,基本上可以确定在ORM中,在多表中,一张表的对象模型,可以包含另外一张表的对象模型。

2.查询操作之:多对一操作

本节的学习,是 员工表emp为例,在java中,对应的有Emp员工对象,基于接口+ 映射文件,总共四个方面的内容(表、接品、映射配置文件、表对象)

在多对一关系中,一个员工对应有一个部门,一个部门在ORM中也是一个对象,所以,在emp对象中,部门dept就是作为员工对象国的一个属性。在本节中,主要解决的问题是:**如何通把查询出来的内容封装到一个对象中的对象属性。**在这里,主要有两种方式:

  • 第一种方式:直接使用手动映射

这种方式比较容易直观理解,其中心思想是:从数据库表获得的字段数据,根据手动映射关系直接传递到对象当中。

<resultMap id="empMapper" type="Emp">
    <id property="empid" column="empid"/>
    <result property="empname" column="empname"/>
    <result property="empage" column="empage"/>
    <association property="dept" javaType="Dept">
        <id property="deptid" column="deptId"/>
        <result property="deptname" column="deptName"/>
    </association>
</resultMap>
  • 第二种方式:嵌套使用手动映射

嵌套使用手动映射也有两种情况:

  1. 在同一个文件中定义另外一个对象的手动映射代码并使用。
  2. 在另外一个文件中定义对象的手动映射代码,并在这个文件中引用

也就是单独把对部门映像结分离出来,其主核心思想是,在配置手动映射的时候,如果一个属性是对象,那么可以针对这个对象定义一个手动配置文件(这个手动映射配置文件可以定义在同一个文件中,也可以定义在另外ORM中与数据库表相关联的映射配置文件中),在查询的映射文件中再通过association标签标签resultMap来使用再次定义的手动配置。

  • 在同一个文件中定义另外一个对象的手动映射代码并且引用
<resultMap id="deptmapperthis" type="Dept">
    <id property="deptid" column="deptId"/>
    <result property="deptname" column="deptName"/>
</resultMap>

引用:

<resultMap id="empMapper2" type="Emp">
    <id property="empid" column="empid"/>
    <result property="empname" column="empname"/>
    <result property="empage" column="empage"/>
    <association property="dept" javaType="Dept" resultMap="deptmapperthis"/>
</resultMap>
  • 在不同文件中定义另外一个对象的手动映射代码并且引用(此时,下面的代码是在dept映射文件中定义的手动映射代码)
<resultMap id="deptmapperthis" type="Dept">
    <id property="deptid" column="deptId"/>
    <result property="deptname" column="deptName"/>
</resultMap>

引用:此时的引用是不同与上面的

<resultMap id="empMapper2" type="Emp">
    <id property="empid" column="empid"/>
    <result property="empname" column="empname"/>
    <result property="empage" column="empage"/>
    <association property="dept" javaType="Dept" 		resultMap="dream.dao.batis.DreamDept.deptmapper"/>
</resultMap>

此地方需注意的是:查询语句必须包含能传递全手动映射代码所必须的字段值

第三种方式:通过查询过得的字段值传递给另外一张表进行查询

在association标签中,还有一个属性:select,他的主要作用是调用其他映射文件的查询语句,其中还有一个属性:column,这个属性的作用是在调用其他映射文件查询语句时,做为一个参数传递过去的。他做为一个参数传递给查询语句时,如果只有一个参数,查询语句接收参数时,可以任意命名。

  • 从emp表中,按字段正常查找字段值
<select id="selectAll" resultMap="empMapper3">
    select empid, empname, empage, deptid
    from emp
</select>
  • 能过手动映射中的association标签中的select属性,执行另外一个映射文件的查询语句,查询语句需要传递参数,这时association标签中的column属性就是要传递的参数值,他是从表中获取得到数据。
<resultMap id="empMapper3" type="Emp">
    <id property="empid" column="empid"/>
    <result property="empname" column="empname"/>
    <result property="empage" column="empage"/>
    <association property="dept" javaType="Dept" select="dream.dao.batis.DreamDept.selectDept" column="deptid"/>
</resultMap>

做为这三种方式,在实际项目中,我们用哪一种呢?

第三种方式是存在缺陷的,他会有多次查询。主要存在以下几种情况

  • 首先查询一次主表(emp )表,获得N条 dept_id记录
  • 对于每一个不同的dept_id,都会去对dept表进行一次查询,可能查询的资料为表的数据记录数
  • 所在,采取这种方式进行查询时,一般查询的次数为:emp表记录数 * dept表记录数

3.查询操作之:一对多操作

在一个对象中,定义另外一个对象集合,在java中可以这样理解,而在数据库表中,一对多可以理解为:通过Group BY 分组后的一个字段,在别的字段中包含有多条记录。

通过本节视频学习总结:一多对,在ORM模型中,一个对象中有一个集合属性,这个集合属性存储的是从另外一张表中获取的数据生成的对象。

一对多的操作也有三种方式,和多对一的方式是一样的

  • 直接定义手动映射文件
<resultMap id="deptnewmapper" type="Dept">
    <id property="deptid" column="deptid"/>
    <result property="deptname" column="deptname"/>
    <collection property="emps"  ofType="Emp">
        <id property="empid" column="empId"/>
        <result property="empname" column="empName"/>
        <result property="empage" column="empAge"/>
    </collection>
</resultMap>
  • 嵌套使用手动映射代码

嵌套使用手动映射代码,也有两种情况,一是直接使用同一文件中的映射代码,另一个是使用外部文件的映射代码

<!-- todo:这个地方是将三个代码都放到一块了 -->

<sql id="newdeptfiled">
    d.deptid,
    d.deptname,
    e.empid 'empId',
    e.empname 'empName',
    e.empage 'empAge'
</sql>

<resultMap id="deptnewmapper2" type="Dept">
    <id property="deptid" column="deptid"/>
    <result property="deptname" column="deptname"/>
    <collection property="emps" ofType="Emp" resultMap="otherdeptmapper"/>
</resultMap>

<resultMap id="otherdeptmapper" type="Emp">
    <id property="empid" column="empId"/>
    <result property="empname" column="empName"/>
    <result property="empage" column="empAge"/>
</resultMap>

<select id="selectAlldept" resultMap="deptnewmapper2">
    select <include refid="newdeptfiled"/>  from dept d left join emp e on d.deptid = e.deptid
</select>
  • 使用单表查询,从单表中得到一个数据再查询另外一张表

其中心思想是:从一张表中得到字段数据,从其中的一个字段数据值做为另一个查询语句的参数,对第二张表进行查询,第二张表查询出来的字段值存储到ORM模型中的对象中,并将其对象传递给一对多对象的集合属性。

具体代码示例如下:

<select id="empselect" parameterType="Integer" resultType="Emp">
    select empid, empname, empage from emp where deptid = #{id}
</select>

<resultMap id="one" type="Dept">
    <id property="deptid" column="deptid"/>
    <result property="deptname" column="deptname"/>
    <collection property="emps" ofType="Emp" select="empselect" column="deptid"/>
</resultMap>

<select id="otherselectdept" resultMap="one">
    select deptid, deptname from dept
</select>

注意:从字段获得数据值传递给第二个查询语句,这个查询语句可以在同一个文件中,也可以在另外一个文件中,只是引用的方式不同,引用外部文件的查询语句时,使用全类名路径文件名 + 查询语句的ID号。

十一、代码生成器

1.简介

mybatis-generator是一款Mybatis插件,能够自动生成Mybatis相关的代码,如:

  • dao接口
  • mapper映射文件
  • 实体类。他可以分为两种:
  1. 与数据库表相对应的java对象
  2. sql条件对象

当然,也在存缺点:

  • 生成的代码默认只支持单表操作
  • 多表查询需要自己编写
  • 对象之间的关系需要自己维护
  • 保存返回主键需要自己添加

2.用法

该插件并不是在开发过程中使用的,而是开发前做的准备工作

在开发之前通过该插件生成相关的代码,然后将生成的代码拷贝到工程中

2.1 添加jar包

使用该插件时,需要添加插件的jar包,但在开发的时候不需要添加,他主要有两个jar包

  • mybatis-generator-core
  • mysql-connector-java(数据库驱动)
  • mybatis-generator-maven-plugin(通过插件)
<plugin>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-maven-plugin</artifactId>
    <version>1.4.0</version>
    <configuration>
        <overwrite>true</overwrite>
        <verbose>true</verbose>
    </configuration>
</plugin>
2.2 配置插件的配置文件
  • 名字为:generatorConfig.xml (说明一)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--配置参考地址:http://www.mybatis.org/generator/configreference/xmlconfig.html-->
    <!--配置参考地址http://blog.csdn.net/pk490525/article/details/16819307-->
    <!--配置参考:http://blog.csdn.net/lirui874125/article/details/49589563-->
    <!--配置参考:http://www.jianshu.com/p/e09d2370b796-->
 
 
    <!-- 引入配置文件 -->
    <!--<properties resource="init.properties"/>-->
    <!-- 指定数据连接驱动jar地址 -->
   <!-- <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />-->
 
    <!--
    context:生成一组对象的环境
    id:必选,上下文id,用于在生成错误时提示
    defaultModelType:指定生成对象的样式
        1,conditional:类似hierarchical;
        2,flat:所有内容(主键,blob)等全部生成在一个对象中;
        3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class)
    targetRuntime:
        1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
        2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
    introspectedColumnImpl:类全限定名,用于扩展MBG
-->
 
    <context id="Mysql" targetRuntime="MyBatis3Simple">
        <!--https://mapperhelper.github.io/docs/3.usembg/,自动生成代码的通用mapper插件-->
        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
            <!-- caseSensitive默认false,当数据库表名区分大小写时,可以将该属性设置为true -->
           <!-- <property name="caseSensitive" value="true"/>-->
        </plugin>
        <!-- 注释 -->
        <commentGenerator >
            <!-- 是否取消自动生成的注释 -->
            <!--<property name="suppressAllComments" value="false"/>-->
            <!-- 是否生成注释代时间戳-->
           <!-- <property name="suppressDate" value="true" />-->
        </commentGenerator>
 
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://192.168.9.107:3306/jack?useUnicode=true&characterEncoding=UTF8&useSSL=true"
                        userId="root"
                        password="root">
            <!-- 针对mysql数据库 -->
            <property name="useInformationSchema" value="true"/>
        </jdbcConnection>
 
        <!-- 类型转换 -->
        <javaTypeResolver >
            <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) -->
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>
 
        <!-- 生成实体类地址 -->
        <javaModelGenerator targetPackage="${targetModelPackage}" targetProject="${targetJavaProject}">
            <!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] -->
            <!--<property name="enableSubPackages" value="true" />-->
            <!-- 是否针对string类型的字段在set的时候进行trim调用 -->
            <!--<property name="trimStrings" value="true" />-->
        </javaModelGenerator>
 
        <!-- 生成mapxml文件 -->
        <sqlMapGenerator targetPackage="${targetXMLPackage}"  targetProject="${targetResourcesProject}">
            <!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] -->
           <!-- <property name="enableSubPackages" value="true" />-->
        </sqlMapGenerator>
 
        <!-- 生成mapxml对应client,也就是接口dao -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="${targetMapperPackage}"  targetProject="${targetJavaProject}">
            <!-- 是否在当前路径下新加一层schema,eg:fase路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] -->
            <!--<property name="enableSubPackages" value="true" />-->
        </javaClientGenerator>
 
        <!-- 配置表信息 -->
        <!-- schema即为数据库名 tableName为对应的数据库表 domainObjectName是要生成的实体类 enable*ByExample
                是否生成 example类   -->
        <!--<table schema="jack" tableName="ALLTYPES" domainObjectName="Customer" >-->
           <!-- <property name="useActualColumnNames" value="true"/>
            <generatedKey column="ID" sqlStatement="DB2" identity="true" />
            <columnOverride column="DATE_FIELD" property="startDate" />-->
            <!-- 忽略列,不生成bean 字段 -->
            <!--<ignoreColumn column="FRED" />-->
            <!-- 指定列的java数据类型 -->
           <!-- <columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" />-->
       <!-- </table>-->
        <table tableName="student"  domainObjectName="Student" />
 
    </context>
</generatorConfiguration>
  • generatorConfig.xml(说明二)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>

    <!--导入属性配置-->
    <properties resource="dbcp-config.properties"/>
    <classPathEntry location="${jdbc.jar}"/>

    <context id="DB2Tables" targetRuntime="MyBatis3">

        <!-- 生成注释为false 不生成为true 【不生成注释时会被重复写入导致报错】 -->
        <commentGenerator>
            <property name="suppressAllComments" value="false"/>
        </commentGenerator>

        <!--jdbc的数据库连接 -->
        <jdbcConnection
                driverClass="${driverClassName}"
                connectionURL="${url}"
                userId="${username}"
                password="${password}">
        </jdbcConnection>

        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>

        <!-- Model模型生成器,用来生成含有主键key的类,记录类 以及查询Example类
        targetPackage     指定生成的model生成所在的包名
        targetProject     指定在该项目下所在的路径
    -->
        <!--<javaModelGenerator targetPackage="com.mmall.pojo" targetProject=".\src\main\java">-->
        <javaModelGenerator targetPackage="com.dream.seeker" targetProject="./src/main/java">
            <!-- 是否允许子包,即targetPackage.schemaName.tableName -->
            <property name="enableSubPackages" value="false"/>
            <!-- 是否对model添加 构造函数 -->
            <property name="constructorBased" value="true"/>
            <!-- 是否对类CHAR类型的列的数据进行trim操作 -->
            <property name="trimStrings" value="true"/>
            <!-- 建立的Model对象是否 不可改变  即生成的Model对象不会有 setter方法,只有构造方法 -->
            <property name="immutable" value="false"/>
        </javaModelGenerator>

        <!--mapper映射文件生成所在的目录 为每一个数据库的表生成对应的SqlMap文件 -->
        <sqlMapGenerator targetPackage="mappers" targetProject="./src/main/resources">
            <property name="enableSubPackages" value="false"/>
        </sqlMapGenerator>

        <!-- targetPackage:mapper接口dao生成的位置 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.*.dao" targetProject="./src/main/java">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false"/>
        </javaClientGenerator>


        <table tableName="admin" domainObjectName="Admin" enableCountByExample="false" enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
            <property name="rootInterface" value="com.*.dao.AdminExtMapper"/>
        </table>

    </context>
</generatorConfiguration>
  • generatorConfig.xml(说明三)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<!-- 配置生成器 -->
<generatorConfiguration>
    <!-- 可以用于加载配置项或者配置文件,在整个配置文件中就可以使用${propertyKey}的方式来引用配置项
    resource:配置资源加载地址,使用resource,MBG从classpath开始找,比如com/myproject/generatorConfig.properties        
    url:配置资源加载地质,使用URL的方式,比如file:///C:/myfolder/generatorConfig.properties.
    注意,两个属性只能选址一个;

    另外,如果使用了mybatis-generator-maven-plugin,那么在pom.xml中定义的properties都可以直接在generatorConfig.xml中使用
<properties resource="" url="" />
 -->
    <properties resource="dbcp-config.properties"/>
    <!-- 在MBG工作的时候,需要额外加载的依赖包
    location属性指明加载jar/zip包的全路径
<classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />
  -->
    <classPathEntry location="/Program Files/IBM/SQLLIB/java/db2java.zip" />

    <!-- 
    context:生成一组对象的环境 
    id:必选,上下文id,用于在生成错误时提示
    defaultModelType:指定生成对象的样式
        1,conditional:类似hierarchical;
        2,flat:所有内容(主键,blob)等全部生成在一个对象中;
        3,hierarchical:主键生成一个XXKey对象(key class),Blob等单独生成一个对象,其他简单属性在一个对象中(record class)
    targetRuntime:
        1,MyBatis3:默认的值,生成基于MyBatis3.x以上版本的内容,包括XXXBySample;
        2,MyBatis3Simple:类似MyBatis3,只是不生成XXXBySample;
    introspectedColumnImpl:类全限定名,用于扩展MBG
-->
    <context id="mysql" defaultModelType="hierarchical" targetRuntime="MyBatis3Simple" >

        <!-- 自动识别数据库关键字,默认false,如果设置为true,根据SqlReservedWords中定义的关键字列表;
        一般保留默认值,遇到数据库关键字(Java关键字),使用columnOverride覆盖
     -->
        <property name="autoDelimitKeywords" value="false"/>
        <!-- 生成的Java文件的编码 -->
        <property name="javaFileEncoding" value="UTF-8"/>
        <!-- 格式化java代码 -->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
        <!-- 格式化XML代码 -->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>

        <!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; -->
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <!-- 必须要有的,使用这个配置链接数据库
        @TODO:是否可以扩展
     -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql:///pss" userId="root" password="admin">
            <!-- 这里面可以设置property属性,每一个property属性都设置到配置的Driver上 -->
        </jdbcConnection>

        <!-- java类型处理器 
        用于处理DB中的类型到Java中的类型,默认使用JavaTypeResolverDefaultImpl;
        注意一点,默认会先尝试使用Integer,Long,Short等来对应DECIMAL和 NUMERIC数据类型; 
    -->
        <javaTypeResolver type="org.mybatis.generator.internal.types.JavaTypeResolverDefaultImpl">
            <!-- 
            true:使用BigDecimal对应DECIMAL和 NUMERIC数据类型
            false:默认,
                scale>0;length>18:使用BigDecimal;
                scale=0;length[10,18]:使用Long;
                scale=0;length[5,9]:使用Integer;
                scale=0;length<5:使用Short;
         -->
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>


        <!-- java模型创建器,是必须要的元素
        负责:1,key类(见context的defaultModelType);2,java类;3,查询类
        targetPackage:生成的类要放的包,真实的包受enableSubPackages属性控制;
        targetProject:目标项目,指定一个存在的目录下,生成的内容会放到指定目录中,如果目录不存在,MBG不会自动建目录
     -->
        <javaModelGenerator targetPackage="com._520it.mybatis.domain" targetProject="src/main/java">
            <!--  for MyBatis3/MyBatis3Simple
            自动为每一个生成的类创建一个构造方法,构造方法包含了所有的field;而不是使用setter;
         -->
            <property name="constructorBased" value="false"/>

            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
            <property name="enableSubPackages" value="true"/>

            <!-- for MyBatis3 / MyBatis3Simple
            是否创建一个不可变的类,如果为true,
            那么MBG会创建一个没有setter方法的类,取而代之的是类似constructorBased的类
         -->
            <property name="immutable" value="false"/>

            <!-- 设置一个根对象,
            如果设置了这个根对象,那么生成的keyClass或者recordClass会继承这个类;在Table的rootClass属性中可以覆盖该选项
            注意:如果在key class或者record class中有root class相同的属性,MBG就不会重新生成这些属性了,包括:
                1,属性名相同,类型相同,有相同的getter/setter方法;
         -->
            <property name="rootClass" value="com._520it.mybatis.domain.BaseDomain"/>

            <!-- 设置是否在getter方法中,对String类型字段调用trim()方法 -->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>


        <!-- 生成SQL map的XML文件生成器,
        注意,在Mybatis3之后,我们可以使用mapper.xml文件+Mapper接口(或者不用mapper接口),
            或者只使用Mapper接口+Annotation,所以,如果 javaClientGenerator配置中配置了需要生成XML的话,这个元素就必须配置
        targetPackage/targetProject:同javaModelGenerator
     -->
        <sqlMapGenerator targetPackage="com._520it.mybatis.mapper" targetProject="src/main/resources">
            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>


        <!-- 对于mybatis来说,即生成Mapper接口,注意,如果没有配置该元素,那么默认不会生成Mapper接口 
        targetPackage/targetProject:同javaModelGenerator
        type:选择怎么生成mapper接口(在MyBatis3/MyBatis3Simple下):
            1,ANNOTATEDMAPPER:会生成使用Mapper接口+Annotation的方式创建(SQL生成在annotation中),不会生成对应的XML;
            2,MIXEDMAPPER:使用混合配置,会生成Mapper接口,并适当添加合适的Annotation,但是XML会生成在XML中;
            3,XMLMAPPER:会生成Mapper接口,接口完全依赖XML;
        注意,如果context是MyBatis3Simple:只支持ANNOTATEDMAPPER和XMLMAPPER
    -->
        <javaClientGenerator targetPackage="com._520it.mybatis.mapper" type="ANNOTATEDMAPPER" targetProject="src/main/java">
            <!-- 在targetPackage的基础上,根据数据库的schema再生成一层package,最终生成的类放在这个package下,默认为false -->
            <property name="enableSubPackages" value="true"/>

            <!-- 可以为所有生成的接口添加一个父接口,但是MBG只负责生成,不负责检查
        <property name="rootInterface" value=""/>
         -->
        </javaClientGenerator>

        <!-- 选择一个table来生成相关文件,可以有一个或多个table,必须要有table元素
        选择的table会生成一下文件:
        1,SQL map文件
        2,生成一个主键类;
        3,除了BLOB和主键的其他字段的类;
        4,包含BLOB的类;
        5,一个用户生成动态查询的条件类(selectByExample, deleteByExample),可选;
        6,Mapper接口(可选)

        tableName(必要):要生成对象的表名;
        注意:大小写敏感问题。正常情况下,MBG会自动的去识别数据库标识符的大小写敏感度,在一般情况下,MBG会
            根据设置的schema,catalog或tablename去查询数据表,按照下面的流程:
            1,如果schema,catalog或tablename中有空格,那么设置的是什么格式,就精确的使用指定的大小写格式去查询;
            2,否则,如果数据库的标识符使用大写的,那么MBG自动把表名变成大写再查找;
            3,否则,如果数据库的标识符使用小写的,那么MBG自动把表名变成小写再查找;
            4,否则,使用指定的大小写格式查询;
        另外的,如果在创建表的时候,使用的""把数据库对象规定大小写,就算数据库标识符是使用的大写,在这种情况下也会使用给定的大小写来创建表名;
        这个时候,请设置delimitIdentifiers="true"即可保留大小写格式;

        可选:
        1,schema:数据库的schema;
        2,catalog:数据库的catalog;
        3,alias:为数据表设置的别名,如果设置了alias,那么生成的所有的SELECT SQL语句中,列名会变成:alias_actualColumnName
        4,domainObjectName:生成的domain类的名字,如果不设置,直接使用表名作为domain类的名字;可以设置为somepck.domainName,那么会自动把domainName类再放到somepck包里面;
        5,enableInsert(默认true):指定是否生成insert语句;
        6,enableSelectByPrimaryKey(默认true):指定是否生成按照主键查询对象的语句(就是getById或get);
        7,enableSelectByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询语句;
        8,enableUpdateByPrimaryKey(默认true):指定是否生成按照主键修改对象的语句(即update);
        9,enableDeleteByPrimaryKey(默认true):指定是否生成按照主键删除对象的语句(即delete);
        10,enableDeleteByExample(默认true):MyBatis3Simple为false,指定是否生成动态删除语句;
        11,enableCountByExample(默认true):MyBatis3Simple为false,指定是否生成动态查询总条数语句(用于分页的总条数查询);
        12,enableUpdateByExample(默认true):MyBatis3Simple为false,指定是否生成动态修改语句(只修改对象中不为空的属性);
        13,modelType:参考context元素的defaultModelType,相当于覆盖;
        14,delimitIdentifiers:参考tableName的解释,注意,默认的delimitIdentifiers是双引号,如果类似MYSQL这样的数据库,使用的是`(反引号,那么还需要设置context的beginningDelimiter和endingDelimiter属性)
        15,delimitAllColumns:设置是否所有生成的SQL中的列名都使用标识符引起来。默认为false,delimitIdentifiers参考context的属性

        注意,table里面很多参数都是对javaModelGenerator,context等元素的默认属性的一个复写;
     -->
        <table tableName="userinfo" >

            <!-- 参考 javaModelGenerator 的 constructorBased属性-->
            <property name="constructorBased" value="false"/>

            <!-- 默认为false,如果设置为true,在生成的SQL中,table名字不会加上catalog或schema; -->
            <property name="ignoreQualifiersAtRuntime" value="false"/>

            <!-- 参考 javaModelGenerator 的 immutable 属性 -->
            <property name="immutable" value="false"/>

            <!-- 指定是否只生成domain类,如果设置为true,只生成domain类,如果还配置了sqlMapGenerator,那么在mapper XML文件中,只生成resultMap元素 -->
            <property name="modelOnly" value="false"/>

            <!-- 参考 javaModelGenerator 的 rootClass 属性 
        <property name="rootClass" value=""/>
         -->

            <!-- 参考javaClientGenerator 的  rootInterface 属性
        <property name="rootInterface" value=""/>
        -->

            <!-- 如果设置了runtimeCatalog,那么在生成的SQL中,使用该指定的catalog,而不是table元素上的catalog 
        <property name="runtimeCatalog" value=""/>
        -->

            <!-- 如果设置了runtimeSchema,那么在生成的SQL中,使用该指定的schema,而不是table元素上的schema 
        <property name="runtimeSchema" value=""/>
        -->

            <!-- 如果设置了runtimeTableName,那么在生成的SQL中,使用该指定的tablename,而不是table元素上的tablename 
        <property name="runtimeTableName" value=""/>
        -->

            <!-- 注意,该属性只针对MyBatis3Simple有用;
            如果选择的runtime是MyBatis3Simple,那么会生成一个SelectAll方法,如果指定了selectAllOrderByClause,那么会在该SQL中添加指定的这个order条件;
         -->
            <property name="selectAllOrderByClause" value="age desc,username asc"/>

            <!-- 如果设置为true,生成的model类会直接使用column本身的名字,而不会再使用驼峰命名方法,比如BORN_DATE,生成的属性名字就是BORN_DATE,而不会是bornDate -->
            <property name="useActualColumnNames" value="false"/>


            <!-- generatedKey用于生成生成主键的方法,
            如果设置了该元素,MBG会在生成的<insert>元素中生成一条正确的<selectKey>元素,该元素可选
            column:主键的列名;
            sqlStatement:要生成的selectKey语句,有以下可选项:
                Cloudscape:相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
                DB2       :相当于selectKey的SQL为: VALUES IDENTITY_VAL_LOCAL()
                DB2_MF    :相当于selectKey的SQL为:SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1
                Derby     :相当于selectKey的SQL为:VALUES IDENTITY_VAL_LOCAL()
                HSQLDB    :相当于selectKey的SQL为:CALL IDENTITY()
                Informix  :相当于selectKey的SQL为:select dbinfo('sqlca.sqlerrd1') from systables where tabid=1
                MySql     :相当于selectKey的SQL为:SELECT LAST_INSERT_ID()
                SqlServer :相当于selectKey的SQL为:SELECT SCOPE_IDENTITY()
                SYBASE    :相当于selectKey的SQL为:SELECT @@IDENTITY
                JDBC      :相当于在生成的insert元素上添加useGeneratedKeys="true"和keyProperty属性
        <generatedKey column="" sqlStatement=""/>
         -->

            <!-- 
            该元素会在根据表中列名计算对象属性名之前先重命名列名,非常适合用于表中的列都有公用的前缀字符串的时候,
            比如列名为:CUST_ID,CUST_NAME,CUST_EMAIL,CUST_ADDRESS等;
            那么就可以设置searchString为"^CUST_",并使用空白替换,那么生成的Customer对象中的属性名称就不是
            custId,custName等,而是先被替换为ID,NAME,EMAIL,然后变成属性:id,name,email;

            注意,MBG是使用java.util.regex.Matcher.replaceAll来替换searchString和replaceString的,
            如果使用了columnOverride元素,该属性无效;

        <columnRenamingRule searchString="" replaceString=""/>
         -->


            <!-- 用来修改表中某个列的属性,MBG会使用修改后的列来生成domain的属性;
            column:要重新设置的列名;
            注意,一个table元素中可以有多个columnOverride元素哈~
          -->
            <columnOverride column="username">
                <!-- 使用property属性来指定列要生成的属性名称 -->
                <property name="property" value="userName"/>

                <!-- javaType用于指定生成的domain的属性类型,使用类型的全限定名
            <property name="javaType" value=""/>
             -->

                <!-- jdbcType用于指定该列的JDBC类型 
            <property name="jdbcType" value=""/>
             -->

                <!-- typeHandler 用于指定该列使用到的TypeHandler,如果要指定,配置类型处理器的全限定名
                注意,mybatis中,不会生成到mybatis-config.xml中的typeHandler
                只会生成类似:where id = #{id,jdbcType=BIGINT,typeHandler=com._520it.mybatis.MyTypeHandler}的参数描述
            <property name="jdbcType" value=""/>
            -->

                <!-- 参考table元素的delimitAllColumns配置,默认为false
            <property name="delimitedColumnName" value=""/>
             -->
            </columnOverride>

            <!-- ignoreColumn设置一个MGB忽略的列,如果设置了改列,那么在生成的domain中,生成的SQL中,都不会有该列出现 
            column:指定要忽略的列的名字;
            delimitedColumnName:参考table元素的delimitAllColumns配置,默认为false

            注意,一个table元素中可以有多个ignoreColumn元素
         <ignoreColumn column="deptId" delimitedColumnName=""/>
         -->
        </table>

    </context>

</generatorConfiguration>
2.3 自动生成代码
  • windows :执行execute.bat
  • Linux:执行execute.sh

将生成的代码直接拷贝到工程当中

2.4 对于xxxExample对象的理解

xxxExample是用于为接口 + 映射文件所形成的操作对象提供查询、增加、修改条件的,这一系列的操作都只针对单表操作,能过类对象进行相关的设置,极大简化的查询条件。

具体代码示例:希望能举一反三

@Test
public void test375() {

    StudentExample studentExample = new StudentExample();
    studentExample.or().andNameEqualTo("showme")
        .andAgeEqualTo(34);
    List<dream.dao.entity.Student> students = studentMapper.selectByExample(studentExample);
    for (dream.dao.entity.Student student : students) {
        System.out.println(student);
    }
}

@Test
public void test388() {
    StudentExample studentExample = new StudentExample();
    studentExample.or().andIdBetween(1,2);
    List<dream.dao.entity.Student> students = studentMapper.selectByExample(studentExample);
    for (dream.dao.entity.Student student : students) {
        System.out.println(student);
    }

}

3.分页插件

在项目开发的过程中,需要对项目过行分页显示,在mybatis中,有一个分页插件PageHelper,他是一款Mybatisr的分页插件。

  • 添加jar包
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.1</version>
</dependency>

<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>4.0</version>
</dependency>
  • 在主配置文件mybatis-config.xml中配置分页属性,具体代码如下:
<!-- 
    plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
    properties?, settings?, 
    typeAliases?, typeHandlers?, 
    objectFactory?,objectWrapperFactory?, 
    plugins?, 
    environments?, databaseIdProvider?, mappers?
-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- config params as the following -->
        <!--<!–分页参数合理化  –>-->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>
  • 在代码中使用
@RequestMapping("/emps")
public String list(@RequestParam(required = false,defaultValue = "1",value = "pn")Integer pn,Map<String,Object> map){

    //引入分页查询,使用PageHelper分页功能
    //在查询之前传入当前页,然后多少记录
    PageHelper.startPage(pn,5);
    //startPage后紧跟的这个查询就是分页查询
    List<Employee> emps = employeeService.getAll();
    //使用PageInfo包装查询结果,只需要将pageInfo交给页面就可以
    PageInfo pageInfo = new PageInfo<>(emps,5);
    //pageINfo封装了分页的详细信息,也可以指定连续显示的页数

    map.put("pageInfo",pageInfo);
    return "list";
}
  • PageInfo类说明
public class PageInfo<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    //当前页
    private int pageNum;
    //每页的数量
    private int pageSize;
    //当前页的数量
    private int size;
 
    //由于startRow和endRow不常用,这里说个具体的用法
    //可以在页面中"显示startRow到endRow 共size条数据"
 
    //当前页面第一个元素在数据库中的行号
    private int startRow;
    //当前页面最后一个元素在数据库中的行号
    private int endRow;
    //总记录数
    private long total;
    //总页数
    private int pages;
    //结果集
    private List<T> list;
 
    //前一页
    private int prePage;
    //下一页
    private int nextPage;
 
    //是否为第一页
    private boolean isFirstPage = false;
    //是否为最后一页
    private boolean isLastPage = false;
    //是否有前一页
    private boolean hasPreviousPage = false;
    //是否有下一页
    private boolean hasNextPage = false;
    //导航页码数
    private int navigatePages;
    //所有导航页号
    private int[] navigatepageNums;
    //导航条上的第一页
    private int navigateFirstPage;
    //导航条上的最后一页
    private int navigateLastPage;
 
    public PageInfo() {
    }
 
    /**
     * 包装Page对象
     *
     * @param list
     */
    public PageInfo(List<T> list) {
        this(list, 8);
    }
 
    /**
     * 包装Page对象
     *
     * @param list          page结果
     * @param navigatePages 页码数量
     */
    public PageInfo(List<T> list, int navigatePages) {
        if (list instanceof Page) {
            Page page = (Page) list;
            this.pageNum = page.getPageNum();
            this.pageSize = page.getPageSize();
 
            this.pages = page.getPages();
            this.list = page;
            this.size = page.size();
            this.total = page.getTotal();
            //由于结果是>startRow的,所以实际的需要+1
            if (this.size == 0) {
                this.startRow = 0;
                this.endRow = 0;
            } else {
                this.startRow = page.getStartRow() + 1;
                //计算实际的endRow(最后一页的时候特殊)
                this.endRow = this.startRow - 1 + this.size;
            }
        } else if (list instanceof Collection) {
            this.pageNum = 1;
            this.pageSize = list.size();
 
            this.pages = this.pageSize > 0 ? 1 : 0;
            this.list = list;
            this.size = list.size();
            this.total = list.size();
            this.startRow = 0;
            this.endRow = list.size() > 0 ? list.size() - 1 : 0;
        }
        if (list instanceof Collection) {
            this.navigatePages = navigatePages;
            //计算导航页
            calcNavigatepageNums();
            //计算前后页,第一页,最后一页
            calcPage();
            //判断页面边界
            judgePageBoudary();
        }
    }
.......
}
;