MySQL5与MySQL8的配置差异

#mysql5
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/test?allowMultiQueries=true
mysql.username=root
mysql.password=root
#mysql8
dataSource.driver=com.mysql.cj.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/test?Timezone=GMT%2B8&useSSL=false&characterEncoding=utf8
dataSource.username=root
dataSource.password=root

Mybatis架构和流程源码分析

整体架构

Mybatis架构

//根据学号查询一个学生
public static void testQueryByNo() throws IOException {
    String resource = "conf.xml";
    Reader reader = Resources.getResourceAsReader(resource);
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
    SqlSession session = sessionFactory.openSession();
    //传入StudentMapper接口,返回该接口的mapper代理对象studentMapper
    StudentMapper studentMapper = session.getMapper(StudentMapper.class);//接口
    //通过mapper代理对象studentMapper,来调用IStudentMapper接口中的方法
    Student student = studentMapper.queryStudentByNo(11);
    System.out.println(student+"****");
    session.close();
}

(1)获取SqlSessionFactory对象

SqlSessionFactory里面最主要的就是parser解析器,通过parseConfiguration()在configuration标签设置了properties、settings、environments等属性标签,并将所有的配置信息放在了Configuration对象中
它还解析了所有的XxxMapper.xml文件,并分析其中的增删改查标签,<select id="" resultType=""> 等属性是通过 parseStatementNode()解析的。且会将XxxMapper.xml中的<select>等标签解析成 MappedStatement对象。即MappedStatement对就是<select>等标签

总结:阅读SqlSessionFactory对象可知,所有的配置信息、增删改标签全部存在于Configuration中,Configuration又存在于DefaultSqlSessionFactory对象中(SqlSessionFactory)
流程SqlSessionFactory对象 -> DefaultSqlSessionFactory -> Configuration -> 包含了一切配置信息

(2)获取SqlSession对象

从源码configuration.newExecutor(tx, execType)->SimpleExecutor可知,该方法会根据不同的类型execType,产生不同的Executor,并且会对执行器进行拦截操作:executor = (Executor) interceptorChain.pluginAll(executor);并且通过装饰模式,将刚才产生的executor包装成一个更加强大的 executor。

作用:以后如果我们要给MyBatis写自己的插件,就可以通过拦截器实现。(插件步骤过程:1写插件、2放入拦截器)
流程SqlSession -> openSession() -> openSessionFromDataSource() -> DefaultSqlSession(configuration,executor,事务)对象 -> 执行SQL

(3)获取XxxMapper对象(代理接口中的方法、mapper.xml中的<select>等标签) + (4)执行<select>等标签中定义的

XxxMapper.queryAll()只是个代理对象的方法,真正执行改queryAll()方法的不是它,而是MapperProxy的invoke(),即用到了JDK动态代理接口InvocationHandler,来代理执行。

mapperMethod.execute(sqlSession,args)可知,实际调用增删改查的方法是依靠了sqlSession中的configuration和executor。
method.convertArgsToSqlCommandParam(args)可知,处理增删改查方法的参数如果是0则return null;如果是1则返回第一个。
从查询方法selectOne() -> selectList():configuration.getMappedStatement()可知,要先获取到用于增删改查的对象。

boundSql是将我们写的SQL参数值进行了拼接后的对象,即最终能被真正执行的SQL。而执行SQL是通过Executor(如果缓存中没有要查询的内容,则进入数据库真实查询:queryFromDatabase())
mybatis使用的jdbc对象是PreparedStatement,底层执行增删改查:PreparedStatement的execute()。

Mybatis四大处理器(StatementHandler、ParameterHandler、TypeHandler、ResultSetHandler)

Mybatis四大核心对象StatementHandler、ParameterHandler、ResultSetHandler、Executor)

特点:
1.都涉及到了拦截器用于增强
2.四大核心对象都包含了该增强操作


设置单个/多个别名

在conf.xml底下的<configuration>增加

    <typeAliases>
        <!-- 单个别名 (别名 忽略大小写) -->
        <!-- <typeAlias type="org.lanqiao.entity.Student" alias="student"/> -->
        <!--  批量定义别名  (别名 忽略大小写),以下会自动将该包中的所有类 批量定义别名: 别名就是类名(不带包名,忽略大小写)   -->
        <package name="org.lanqiao.entity"/>
    </typeAliases>

SpringBoot方法:

mybatis:
  # mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.demo.entity

resultMap

resultMap可以实现2个功能:

  1. 类型转换(要实现接口 比较麻烦,个人思路:Mybatis-plus使用注解 @TableField(exist = false) 注明非数据库字段属性)
  2. 属性-字段的映射关系

a.创建转换器:需要实现TypeHandler接口(继承BaseTypeHandler)

package org.lanqiao.converter;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

//BaseTypeHandler<java类型>
public class BooleanAndIntConverter extends BaseTypeHandler<Boolean>{

    //java(boolean)-DB(number)
    /*
     * ps:PreparedStatement对象
     * i:PreparedStatement对象操作参数的位置
     * parameter:java值
     * jdbcType:jdbc操作的数据库类型
     */
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType)
            throws SQLException {
            if(parameter) {
                ps.setInt(i, 1); 
            }else {
                ps.setInt(i, 0); 
            }
    }

    //db(number)->java(boolean)
    @Override
    public Boolean getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return sexNum == 1?true:false ;
    }

    @Override
    public Boolean getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        int sexNum = rs.getInt(columnIndex) ;//rs.getInt(1)
        return sexNum == 1?true:false ;
    }

    @Override
    public Boolean getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        int sexNum = cs.getInt(columnIndex) ;//rs.getInt(1)
        return sexNum == 1?true:false ;
    }
}

b.配置conf.xml

    <typeHandlers>
        <typeHandler handler="org.lanqiao.converter.BooleanAndIntConverter" javaType="Boolean" jdbcType="INTEGER" />
    </typeHandlers>

c.XxxMapper.xml中使用

    <select id="queryStudentByStunoWithConverter"     parameterType="int"   resultMap="studentResult" >
        select * from student where stuno = #{stuno}
    </select>
    
    <resultMap type="student" id="studentResult">
            <!-- 分为主键id 和非主键 result-->
            <id property="stuNo"  column="stuno"  />
            <result property="stuName"  column="stuname" />
            <result property="stuAge"  column="stuage" />
            <result property="graName"  column="graname" />
            <result property="stuSex"  column="stusex"  javaType="boolean" jdbcType="INTEGER"/>
    </resultMap>

resultType+HashMap实现属性-字段的映射关系

colunm-entity

<select id="queryStudentByIdWithHashMap"      parameterType="int"    resultType="student" >
    select id "stuNo",name "stuName" from student where id = #{id}
</select>

ResultMap(EasyCode中的写法)

<select id="queryById" resultMap="UserMap">
    select id, name, age from test.user where id = #{id}
</select>

<resultMap type="com.example.demo.entity.User" id="UserMap">
    <!--没有另外区分主键,数据也能查找-->
    <result property="id" column="id" jdbcType="INTEGER"/>
    <result property="name" column="name" jdbcType="VARCHAR"/>
    <result property="age" column="age" jdbcType="INTEGER"/>
</resultMap>

resultMap鉴别器

场景:学生表(sno,sname,ssex,shigh,sweigh),需要用鉴别器实现在entity字段中有一个character,如果是男生则显示身高如果是女生则显示体重。

<resultMap type="com.example.demo.entity.Student" id="StudentMap">
    <result property="sno" column="sno" jdbcType="INTEGER"/>
    <result property="sname" column="sname" jdbcType="VARCHAR"/>
    <result property="ssex" column="ssex" jdbcType="BOOLEAN"/>
    <result property="shigh" column="shigh" jdbcType="INTEGER"/>
    <result property="sweigh" column="sweigh" jdbcType="INTEGER"/>
    <result property="scharacter" column="scharacter" jdbcType="INTEGER"/>
    <!--鉴别器,对查询结果进行分支处理:如果是男则特征为身高,如果是女则特征为体重-->
    <!--ssex在数据库中的类型是tinyint(1),可以直接默认为Boolean类型-->
    <discriminator javaType="Boolean" column="ssex">
        <case value="true" resultType="com.example.demo.entity.Student">
            <result column="shigh" property="scharacter"/>
        </case>
        <case value="false" resultType="com.example.demo.entity.Student">
            <result column="sweigh" property="scharacter"/>
        </case>
    </discriminator>
</resultMap>

两种取值符号

#{属性名} :防止SQL注入(一般情况用这个)
'${属性名}' :模糊查询常用select * from student where stuname like '%${stuName}%'
简单类型下第二种方式为${value},value为固定名称
两者都支持属性级联:
select * from student where homeaddress = #{address.homeAddress}


存储过程

1.在数据库中运行存储过程sql

create or replace procedure queryCountByGradeWithProcedure(gName in varchar, scount out number )
as
begin 
    select count(*) into scount from student where graname = gname ;
end;
/

2.在mapper.xml调用存储过程
通过statementType="CALLABLE"设置SQL的执行方式是存储过程。

<!-- 通过调用[存储过程] 实现查询 ,statementType="CALLABLE"
    存储过程的输入参数,在mybatis用Map来传递(HashMap)
-->
<select id="queryCountByGradeWithProcedure" statementType="CALLABLE"  parameterType="HashMap" >
    {
        CALL queryCountByGradeWithProcedure(
            #{gName,jdbcType=VARCHAR,mode=IN},
            #{scount,jdbcType=INTEGER,mode=OUT}
        ) 
    }    
</select>

3.在mapper接口写入
存储过程的输入参数gName需要通过HashMap来指定
在使用时,通过hashmap的put方法传入输入参数的值;通过hashmap的Get方法 获取输出参数的值。

Map<String,Object> params = new HashMap<>();//通过map给 存储过程指定输入参数
params.put("gName", "g1") ;//指定存储过程的输入参数gName的值是g1
studentMapper.queryCountByGradeWithProcedure(params);//调用存储过程,并传入输入参数
Object count = params.get("scount") ;//获取存储过程的输出参数

动态SQL

<where>会自动处理符合条件的(如:非空)第一个<if>标签中的and,但不会处理之后<if>中的and

<select id="queryStuByNOrAWishSQLTag"      parameterType="student"    resultType="student" >
    select stuno,stuname,stuage from student
    <where>
        <!-- <if test="student有stuname属性 且不为null"> -->
        <if test="stuName !=null and stuName!=''  "> 
            and stuname = #{stuName}
        </if>
        <if test="stuAge !=null and stuAge!=0  "> 
             and stuage = #{stuAge}
        </if>
    </where>
</select>

trim

功能比where更强大,他不仅可以智能去掉开头第一个多余的判断(不仅仅是and),还可以去掉结尾多余的判断。
其中prefix是拼接头、prefixOverrides是删除头判断、suffixOverrides是删除尾判断

<select id="queryAll3" resultMap="StudentMap">
    select
    sno, sname, ssex, shigh, sweigh, scharacter from test.student
   <trim prefix="where" prefixOverrides="and">
        <if test="sno != null">
            and sno = #{sno}
        </if>
        <if test="sname != null and sname != ''">
            and sname = #{sname}
        </if>
        <if test="ssex != null">
            and ssex = #{ssex}
        </if>
        <if test="shigh != null">
            and shigh = #{shigh}
        </if>
        <if test="sweigh != null">
            and sweigh = #{sweigh}
        </if>
        <if test="scharacter != null">
            and scharacter = #{scharacter}
        </if>
   </trim>
</select>

<foreach>查询学号为1、2、53的学生信息

拼接过程:

select * from student 
open:
select * from student and  stuno in (
item+separator:
select * from student and  stuno in (1,2,53
close:
select * from student and  stuno in (1,2,53)

1.对象属性

<select id="queryStudentsWithNosInGrade"  parameterType="grade" resultType="student">
    select * from student 
    <where>
         <if test="stuNos!=null and stuNos.size>0">
            <foreach collection="stuNos" open=" and  stuno in (" close=")" 
                item="stuNo" separator=",">   
                #{stuNo}
            </foreach>
         </if>
    </where>
</select>

2.简单类型的数组:
无论编写代码时,传递的是什么参数名(stuNos),在mapper.xml中 必须用array代替该数组

<!-- 将多个元素值 放入数组中 int[] stuNos = {1,2,53} -->
<select id="queryStudentsWithArray"  parameterType="int[]" resultType="student">
    select * from student 
    <where>
         <if test="array!=null and array.length">
            <foreach collection="array" open=" and  stuno in (" close=")" 
                item="stuNo" separator=",">   
                #{stuNo}
            </foreach>
         </if>
    </where>
</select>

3.集合:
无论编写代码时,传递的是什么参数名(stuNos),在mapper.xml中 必须用list代替该数组

<!-- 将多个元素值 放入数组中 List<Integer> stuNos 值 {1,2,53} -->
<select id="queryStudentsWithList"  parameterType="list" resultType="student">
    select * from student 
    <where>
         <if test="list!=null and list.size>0">
            <foreach collection="list" open=" and  stuno in (" close=")" 
                item="stuNo" separator=",">   
                #{stuNo}
            </foreach>
         </if>
    </where>
</select>

4.SQL片段+对象数组

<!--    sql片段-->
<sql id="objectArrayStunos">
    <where>
         <if test="array!=null and array.length>0">
            <foreach collection="array" open=" and  stuno in (" close=")" 
                item="student" separator=",">   
                #{student.stuNo}
            </foreach>
         </if>
    </where>
</sql>

<!-- 将多个元素值 放入对象数组中Student[] students = {student0,student1,student2}  每个studentx包含一个学号属性 -->
<select id="queryStudentsWithObjectArray"  parameterType="Object[]" resultType="student">
    select * from student
    <!--如果sql片段和  引用处不在同一个文件中,则需要 在refid 引用时  加上namespace:   namespace.id-->
    <!--<include refid="org.lanqiao.mapper.abcMapper.objectArrayStunos"></include> -->
    <include refid="objectArrayStunos"></include>
</select>

表关联查询

一对一

【场景】:根据学号(int)查询指定学生的所有Student信息和StudentCard信息(两个表)
【做法一】:新建一个StudentBusiness的学生业务扩展实体类,继承一个属性多的,重写一个属性少的。

public class StudentBusiness  extends Student{
    private int cardId;
    private String cardInfo ;
    //StudentCard属性...
}
<select id="queryStudentByNoWithOO"     parameterType="int"      resultType="StudentBusiness" >
     select s.*,c.* from student s  inner join studentcard c
     on s.cardid=c.cardid
     where s.stuno = #{stuNo}
</select>

【做法二】:Student类中增加StudentCard类属性,通过resultMap连接

public class Student {
    //1学生信息
    private int stuNo ;
    private String stuName ;
    //2学生证信息
    private StudentCard card ;
}
 <!-- 利用resultMap实现一对一 -->
<select id="queryStudentByNoWithOO2"  parameterType="int" 
 resultMap="student_card_map" >
    select s.*,c.* from student s  inner join studentcard c
    on s.cardid=c.cardid
    where s.stuno = #{stuNo}
</select>

<resultMap type="student" id="student_card_map">
        <!-- 学生的信息 -->
        <id  property="stuNo" column="stuNo"/>
        <result property="stuName" column="stuName" />
        <result property="stuAge" column="stuAge" />
        <!-- 一对一时,对象成员使用 association映射;javaType指定该属性的类型-->
        <association property="card" javaType="StudentCard" >
                <id property="cardId" column="cardId"/>
                <result property="cardInfo" column="cardInfo"/>
        </association>
</resultMap>

一对多

public class StudentClass {
    private int classId;
    private String className;
    //增加学生属性 (通过该字段 让Student类和StudentClass类建立起关联)
    private List<Student> students ;
}
<!--    一对多 查询g1班的班级信息,和g1班的所有学生信息 -->
<select id="queryClassAndStudents" parameterType="int" resultMap="class_student_map">
    select  c.*,s.* from student s
    inner join studentclass c 
    on c.classid = s.classid
    where c.classid = #{classId}
</select>
    
<!-- 类-表的对应关系 -->
<resultMap type="studentClass" id="class_student_map">
        <!-- 因为 type的主类是班级,因此先配置班级的信息-->
        <id  property="classId" column="classId"/>
        <result  property="className" column="className"/>
        <!-- 配置成员属性学生,一对多;属性类型:javaType,属性的元素类型ofType -->
        <collection property="students" ofType="student">
            <id  property="stuNo" column="stuNo"/>
            <result  property="stuName" column="stuName"/>
            <result  property="stuAge" column="stuAge"/>
        </collection>
</resultMap>

Log4j日志

a.conf.xml中开启 如果不指定,Mybatis就会根据以下顺序 寻找日志
SLF4JApache Commons LoggingLog4j2Log4jJDK logging

<settings>
    <!-- 开启日志,并指定使用的具体日志 -->
    <setting name="logImpl" value="LOG4J"/>
</settings>

b.编写配置日志输出文件log4j.properties
日志级别(建议在开发时设置debug,在运行时设置为info或以上):
DEBUG < INFO < WARN < ERROR

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

一级缓存、二级缓存

一级缓存(同一个SqlSession对象)

MyBatis默认开启一级缓存,如果用同样的SqlSession对象查询相同的数据,则只会在第一次查询时向数据库发送SQL语句,并将查询的结果放入到SqlSession中(作为缓存在)。后续再次查询该同样的对象时,则直接从缓存中查询该对象即可(即省略了数据库的访问)。
session.commit();时会自动清除一级缓存。

Reader reader = Resources.getResourceAsReader("conf.xml") ;
SqlSessionFactory sessionFacotry = new SqlSessionFactoryBuilder().build(reader,"development") ;
SqlSession session = sessionFacotry.openSession() ;
StudentMapper studentMapper = session.getMapper(StudentMapper.class) ;
Student student = studentMapper.queryStudentByStuno(1) ;
//session.commit();
Student student2 = studentMapper.queryStudentByStuno(2) ;
System.out.println(student);
System.out.println(student2);
session.close();

从控制台的日志中可看出,SQL只执行了一次。若中间插入session.commint()则执行两次SQL操作。

二级缓存(MyBatis默认关闭二级缓存,需要手工打开)

MyBatis的二级缓存是将对象放入硬盘文件中的序列化操作。
触发将对象写入二级缓存的时机:SqlSession对象的session.close()方法。
一、Mybatis自带的二级缓存
Mybatis自带二级缓存:【同一个namespace】生成的mapper对象(namespace的值就是接口的全类名包名.类名, 通过接口可以产生代理对象(studentMapper对象)),即:只要产生的xxxMapper对象来自于同一个namespace,则这些对象共享二级缓存。
a.在conf.xml下,<settings></settings>中追加声明

<settings>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

b.在具体的mapper.xml中声明开启(如studentMapper.xml中)

<mapper namespace="org.lanqiao.mapper.StudentMapper">
    <!-- 声明此namespace开启MyBatis自带的二级缓存-->
    <cache/>
    ...
</mapper>

c.准备缓存的对象,必须实现了序列化接口 (如果开启的缓存Namespace="org.lanqiao.mapper.StudentMapper"),可知序列化对象为Student,因此需要将Student序列化 (序列化Student类,以及Student的级联属性、和父类)

public class Student implements Serializable{
    private StudentCard card;//StudentCard也需要序列化
}

d.禁用二级缓存
select标签中useCache="false"
e.清理二级缓存
(1)与清理一级缓存的方法相同,session.commit()(一般执行增删改时会清理掉缓存;设计的原因是为了防止脏数据
在二级缓存中,session.commit()不能是查询自身的commit

        Reader reader = Resources.getResourceAsReader("conf.xml") ;
        SqlSessionFactory sessionFacotry = new SqlSessionFactoryBuilder().build(reader,"development") ;
        
        SqlSession session = sessionFacotry.openSession() ;
        StudentMapper studentMapper = session.getMapper(StudentMapper.class) ;
        Student student = studentMapper.queryStudentByStuno(1) ;
//        session.commit();//使用查询自身的commit
        session.close();//进行缓存的时刻
        
        //增删改 
        SqlSession updateSession = sessionFacotry.openSession() ;
        StudentMapper updateMapper = updateSession.getMapper(StudentMapper.class) ;
        Student updateStuent = new Student();
        updateStuent.setStuNo(2);
        updateStuent.setStuName("lxs");
        updateStuent.setGraName("ggg");
        updateMapper.updateStudentByStuno(updateStuent); ;
        updateSession.commit();//commit会清理一级和二级缓存
        
        //第二次查询
        SqlSession session2 = sessionFacotry.openSession() ;
        StudentMapper studentMapper2 = session2.getMapper(StudentMapper.class) ;
        Student student2 = studentMapper2.queryStudentByStuno(1) ;
        session2.close();

(2)在select标签中 增加属性 flushCache="true",刷新缓存(第二次命中率还是显示0.5)
三方提供/自定义的二级缓存(ehcache、memcache)
要想整合三方提供的二级缓存 (或者自定义二级缓存),必须实现org.apache.ibatis.cache.Cache接口,该接口的默认实现类是PerpetualCache。第三方已经实现,只需调用即可。
a.增加依赖

<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache-core -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
</dependency>

b.编写ehcache配置文件Ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="">
  <!--当二级缓存的对象 超过内存限制时(缓存对象的个数>maxElementsInMemory),存放入的硬盘文件  -->
 <diskStore path="D:\Ehcache"/>
 <!-- 
     maxElementsInMemory:设置 在内存中缓存 对象的个数
    maxElementsOnDisk:设置 在硬盘中缓存 对象的个数
    eternal:设置缓存是否 永远不过期
    overflowToDisk:当内存中缓存的对象个数 超过maxElementsInMemory的时候,是否转移到硬盘中
    timeToIdleSeconds:当2次访问 超过该值的时候,将缓存对象失效 
    timeToLiveSeconds:一个缓存对象 最多存放的时间(生命周期)
    diskExpiryThreadIntervalSeconds:设置每隔多长时间,通过一个线程来清理硬盘中的缓存
    memoryStoreEvictionPolicy:当超过缓存对象的最大值时,处理的策略;LRU,FIFO,LFU
  -->             
 
 <defaultCache
  maxElementsInMemory="1000"
  maxElementsOnDisk="1000000"
  eternal="false"
  overflowToDisk="false"
  timeToIdleSeconds="100"
  timeToLiveSeconds="100"
  diskExpiryThreadIntervalSeconds="120"
  memoryStoreEvictionPolicy="LRU">
 </defaultCache>
</ehcache>

c.在xxxMapper.xml中开启EhCache二级缓存

<mapper namespace="org.lanqiao.mapper.StudentMapper">
    <cache  type="org.mybatis.caches.ehcache.EhcacheCache">
        <!-- 通过property覆盖Ehcache.xml中的值 -->
        <property name="maxElementsInMemory" value="2000"/>
        <property name="maxElementsOnDisk" value="3000"/>
    </cache>
</mapper>

逆向工程

表、类、接口、mapper.xml四者密切相关,因此 当知道一个的时候 其他三个应该可以自动生成。
一般:表->其他三个
a.所需依赖

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.3.5</version>
</dependency>

b.配置generator.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>
    <context id="DB2Tables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!--
                     suppressAllComments属性值:
                         false:自动生成实体类、SQL映射文件时没有注释
                         true:自动生成实体类、SQL映射文件,并附有注释
                   -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>


        <!-- 数据库连接信息 -->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=Hongkong"
                        userId="root" password="123456">
        </jdbcConnection>
        <!--
                  forceBigDecimals属性值:
                      true:把数据表中的DECIMAL和NUMERIC类型,
      解析为JAVA代码中的java.math.BigDecimal类型
                      false(默认):把数据表中的DECIMAL和NUMERIC类型,
      解析为解析为JAVA代码中的Integer类型
              -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--
               targetProject属性值:实体类的生成位置
               targetPackage属性值:实体类所在包的路径
           -->
        <javaModelGenerator targetPackage="cn.ctx.entity" targetProject="./src/main/java">
            <!-- trimStrings属性值:
                      true:对数据库的查询结果进行trim操作
                      false(默认):不进行trim操作
                    -->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--
               targetProject属性值:SQL映射文件的生成位置
               targetPackage属性值:SQL映射文件所在包的路径
           -->
        <sqlMapGenerator targetPackage="cn.ctx.mapper" targetProject="./src/main/java">
        </sqlMapGenerator>
        <!-- 生成动态代理的接口  -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="cn.ctx.mapper" targetProject="./src/main/java">
        </javaClientGenerator>

        <!-- 指定数据库表  -->
        <table tableName="student"></table>
        <table tableName="studentcard"></table>
    </context>
</generatorConfiguration>

c.执行main函数

package cn.ctx;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;

public class Test {
    public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
        File file = new File("src/main/resources/generator.xml") ;//配置文件
        List<String> warnings = new ArrayList<String>();
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(file);
        DefaultShellCallback callBack = new DefaultShellCallback(true);
        //逆向工程的核心类
        MyBatisGenerator generator = new MyBatisGenerator(config, callBack,warnings  );
        generator.generate(null);
    }
}

d.执行方法
Mybatis的逆向工程有一点特别之处就是会在Entity中生产XxxExample,而一系列的条件限定都是通过这里定义的。

public class TestGeneratorDemo {

    public static void main(String[] args) throws IOException {
        String resource = "conf.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        SqlSession session = sessionFactory.openSession();
        StudentMapper mapper = session.getMapper(StudentMapper.class);
        //规则:example 默认使用的是 第一个criteria
        StudentExample example = new StudentExample() ;
        StudentExample.Criteria criteria = example.createCriteria();
        criteria.andStunameLike("%l%");
        StudentExample.Criteria criteria1 = example.createCriteria();
        criteria1.andStunoLessThanOrEqualTo((short)31);
        criteria1.andGranameLike("%j%") ;
        example.or(criteria1) ;
        List<Student> students = mapper.selectByExample(example ) ;
        System.out.println(students );
        session.close();
    }
}

下一篇章:Mybatis细节与进阶

Last modification:June 12th, 2021 at 05:20 pm
喵ฅฅ