1. MyBatis 数据库环境切换
-
在实际的开发过程中,数据库环境的切换很有实用性, 比如, 在开发的时候, 使用的是测试数据库, 但是最后快上线的时候, 就需要在模拟实际环境的数据库上进行压力测试, 最后部署在服务器上之后, 使用生产数据库, 或者在数据库之间迁移也很使用, 只需要配置environments下的 environment 即可, 使用时通过 environments 下面的 default 来指定即可
-
在conf.xml总配置中配置mysql, oracle数据库之间的切换环境
<!-- 该标签中可配置数据库信息, 如连接oracle, mysql 等; 可通过default来指定所选择的环境 -->
<environments default="devMySQL">
<!-- MySQL 数据库 -->
<environment id="devMySQL">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 配置数据库信息; 可以直接在value中写死, 也可以将值以kv对的形式写在properties文件中,
以类似于EL表法是的方式动态引入(需要先加入 properties标签, 引入properties文件)-->
<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>
<!-- Oracle 数据库 -->
<environment id="devOracle">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 配置数据库信息; 可以直接在value中写死, 也可以将值以kv对的形式写在properties文件中,
以类似于EL表法是的方式动态引入(需要先加入 properties标签, 引入properties文件)-->
<property name="driver" value="${oracle.driver}"/>
<property name="url" value="${oracle.url}"/>
<property name="username" value="${oracle.username}"/>
<property name="password" value="${oracle.password}"/>
</dataSource>
</environment>
</environments>
- 在src下配置properties文件
# MySQL 数据库
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://127.0.0.1:3306/MyBatisTest?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true&noAccessToProcedureBodies=true
mysql.username=root
mysql.password=123456
# Oracle 数据库
oracle.driver=oracle.jdbc.OracleDriver
oracle.url=jdbc:oracle:thin:@127.0.0.1:1521:MyBatisTest
oracle.username=root
oracle.password=123456
- 在conf.xml中配置数据库支持类(即配置 Provider 别名)
<!-- 配置数据库支持类: 大小写必能乱写 -->
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
<!--<property name="SQL Server" value=""/>-->
</databaseIdProvider>
- 根据数据库的特性在mapper.xml中编写对应的SQL语句; 并加上 databaseId="Provider别名"
<!-- oracle -->
<select id="queryStudentByNo" resultType="Student" parameterType="int" dataBaseid="oracle">
select * from student where stuNo=#{stuNo}
</select>
<!-- mysql -->
<select id="queryStudentByNo" resultType="Student" parameterType="int" dataBaseid="mysql">
select * from student where stuNo=#{stuNo}
</select>
- 注意: 如果 mapper.xml 的SQL标签仅有一个不带 databaseId的标签, 则该标签自动适应当前数据库; 如果既有不带databaseId的标签, 又有带databaseId的标签, 则程序会优先使用带databaseId的标签
2. 增删改的返回值问题
-
由于xml中增删改没有resultType标签所以不能定义返回值类型, 解决办法就是:只需要在接口的增删改抽象方法中加上返回值类型即可
-
返回值可以是: void, Integer, Long, Boolean
-
例子:
<!-- mapper.xml中增加的SQL没有返回值 -->
<!-- 增加 -->
<insert id="addStudent" parameterType="Student" >
insert into student(stuNo, stuName, stuAge, graName, stuSex) values(#{stuNo}, #{stuName}, #{stuAge}, #{graName}, #{stuSex})
</insert>
// 增加学生
Boolean addStudent(Student student);
// 测试代码
public static void addStudent() throws IOException {
SqlSession sqlSession = getSqlSession();
// 下面两句代码是使用动态代理方式
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student1 = new Student(3, "ju", 20, "class3", false);
Boolean aBoolean = studentMapper.addStudent(student1);
if(aBoolean){
System.out.println("增加成功!");
} else{
System.out.println("增加失败!");
}
// 下面两句是使用MyBatis基础CRUD的方式, 不提倡了以及, 知道怎么用就可以了
// String stmt = "xyz.xmcs.Entity.studentMapper.addStudent";
// Student student = new Student(2, "ls", 20, "class1");
// int insert = sqlSession.insert(stmt, student);
sqlSession.commit();
// System.out.println("Insert Result:" + insert);
sqlSession.close();
}
3. MyBatis注解
3.1 使用 @Select 注解
-
首先修改 conf.xml 文件中的一些配置, 可通过 class或package选之一来指定
-
mapper中使用class属性指定有注解的接口 <\mapper class="xyz.xmcs.mapper.StudntMapper" />
-
或者使用package指定studentMapper接口所在的包(批量注册:可以一次性引入注解接口和mapper文件) <\package name="xyz.xmcs.mapper"/>
-
-
修改 StudentMapper.java 接口中的相关方法
@Select("select * from student where stuNo=#{stuNo}")
Student queryStudentByNo(int stuNo);
-
总结:
-
将 sql 语句写在接口的方法上@select("SQL语句")
-
将接口的全类名写入总配置conf.xml的<\mapper>, 让mybatis知道SQL语句
-
4. 事务自动提交问题
-
手动提交: 当 sessionFactory.openSession() 中形参为空的时候就是手动提交, 当执行增删改的时候, 需要通过 session.commit() 进行手动提交
-
自动提交: 当 sessionFactory.openSession(true) 中参数为true时, 就是自动提交, 每个DML语句都会自动提交
5. 自增问题
Mysql 本身支持自增
-
mysql 本身支持自增,如果在数据库表中主键支持自增, 在对应的Been中, 自增的类型不能是 int, 必须是 Integer, 因为Integer是对象, 可以为null
-
mysql 数据库自增的值回写:(在需要回写的SQL语句标签中增加)
-
使用主键生成策略: useGeneratedKeys="true"
-
指定回写的值: keyProperty="stuNo"
-
-
示例
<!-- 增加: mysql 自增回写主键 -->
<insert id="addStudent" parameterType="Student" useGeneratedKeys="true" keyProperty="stuNo">
insert into student(stuNo, stuName, stuAge, graName, stuSex) values(#{stuNo}, #{stuName}, #{stuAge}, #{graName}, #{stuSex})
</insert>
Oracle 不支持自增
-
Oracle本身不支持自增, 但是可以通过序列来模拟自增
-
序列自带两个属性:
-
nextval: 序列中的下一个值
-
currval: 当前值
-
-
Oracle 自增实现
-- ORacle 创建自增序列
create sequence myseq increment by 1 start with 1;
-- 使用自增序列
insert into student values(myseq.nestval, 'zs',23,'a1');
insert into student values(myseq.nestval, 'ls',24,'a2');
insert into student values(myseq.nestval, 'ww',25,'a1');
- oracle 自增回写: 通过<\insert>的子标签<\selectKey>实现, 在<\selectKey>中查询下一个序列(自增后的值), 再将此值传入 keyProperty="stuNo"属性, 最后在真正执行时, 使用该属性值
<!-- 增加: oracle 自增回写主键 第一种方式 -->
<insert id="addStudent" parameterType="Student" databaseId="oracle">
<selectKey keyProperty="stuNo" resultType="Integer" order="BEFORE">
select myseq.nextval from dual
</selectKey>
insert into student(stuNo, stuName, stuAge, graName, stuSex) values(#{stuNo}, #{stuName}, #{stuAge}, #{graName}, #{stuSex})
</insert>
<!-- 增加: oracle 自增回写主键 第二种方式 -->
<insert id="addStudent" parameterType="Student" databaseId="oracle">
<selectKey keyProperty="stuNo" resultType="Integer" order="AFTER">
select myseq.currval from dual
</selectKey>
insert into student(stuNo, stuName, stuAge, graName, stuSex) values(#{myseq.noetval}, #{stuName}, #{stuAge}, #{graName}, #{stuSex})
</insert>
6. 参数问题
-
以目前学的知识, 将多个参数封装成一个JavaBeen对象(pojo), 然后使用该对象传递参数(我觉得还是这种形式比较好)
-
如果采用传入多个参数, 就不用再mapper.xml中写 parameterType
-
以规定的形式传入多个简单形参(也可以通过注解解决这个问题): 只能是 arg0, arg1 ...; 或者 param0, param1, param2 ... 这样的形式
<!-- insert SQL标签没有返回值, 如果不使用javaBeen来封装参数的化, 就不要写 parameterType属性; 并且使用规定的形参形式 -->
<insert id="addStudentWithParams" databaseId="mysql">
insert into student(stuNo, stuName, stuAge, graName) values (#{param1}, #{param2}, #{param3}, #{param4})
</insert>
// 增加学生的抽象方法, 这里可以定义返回值, 最好使用包装类类型
int addStudentWithParams(Integer stuNo, String stuName, Integer stuAge, Stirng graName);
-
命名参数(通过注解使用自定义形参)
- 原理就是再接口方法的参数中通过 @Param("stuNo") 指定SQL中参数的名字
// 增加学生的抽象方法, 这里可以定义返回值, 最好使用包装类类型
int addStudentWithParams(@Param("stuNo")Integer stuNo, @Param("stuName")String stuName, @Param("stuAge")Integer stuAge, @Param("graName")Stirng graName);
<!-- insert SQL标签没有返回值, 如果不使用javaBeen来封装参数的化, 就不要写 parameterType属性; 使用自定义形参形式 -->
<insert id="addStudentWithParams" databaseId="mysql">
insert into student(stuNo, stuName, stuAge, graName) values (#{stuNo}, #{stuName}, #{stuAge}, #{graName})
</insert>
- 综合使用(参数是简单形式加对象形式)
// 增加学生的抽象方法, 参数的传递是通过简单类型传递stuNo, 通过Student传递其他参数
int addStudentWithParamAndObject(@Param("stuNo")Integer stuNo, @Param("stu")Student student);
<!-- insert SQL标签没有返回值, 如果不使用javaBeen来封装参数的化, 就不要写 parameterType属性; 拿的不是参数本身, 而是参数值里面的属性值-->
<insert id="addStudentWithParams" databaseId="mysql">
insert into student(stuNo, stuName, stuAge, graName) values (#{stuNo}, #{stu.stuName}, #{stu.stuAge}, #{stu.graName})
</insert>
7. 增加 null
-
oracle: 如果插入的字段是Null, 提示错误: 应该是Other类型而不是null
-
mysql: (在不考虑非空约束的情况下)如果插入字段值是Null, 可以正常执行
-
原因: 各个数据库再MyBatis中对各种数据类型的默认值不一致; MyBatis 中, jdbcTypeForNull(如果是Null), 则默认值是OTHER, 对于Other来说, MySQL可以处理, 但是Oracle不能处理
-
解决Oracle数据库Null问题: 目的就是告诉Oracle, Other --就是--> Null(下面两种方式)
-
方式一: 当某个数据类型oracle无法处理时, 告诉他默认值就是NULL; 即直接在可能出现Null的SQL形参中(#{})指定 jdbcType=NULL; 如: #{stuName, jdbcType=NULL}; 这句话的效果就是, 当Oracle无法解析这个参数时就使用NULL, 但是能正常解析的话就不管NULL(就是不会影响正常的值)
-
方式二: 配置MyBatis全局配置文件conf.xml; 在settings 中增加 <\setting name="jdbcTypeForNull" value="NULL" />
-
8. 返回值为HashMap的情况
-
已经使用过了, 就是在写SQL语句时, 查询的每个字段起个别名, 返回值resultType指定是HashMap形式; 并且一个实例对应一个Map,要获取多个实例的话, 就需要使用List
- 起别名的SQL: select stuNo 'id', stuName 'name', stuAge 'age' from student where stuNo=#
-
如果在查询的时候不起别名, 那么Map获取到的值的key就是数据表的字段名
- 不起别名的SQL: select stuNo, stuName, stuAge from student where stuNo=#
-
使用HashMap接收返回的多个值, 并且 key 是指定的数据表字段名, value是对应的返回值; (关键就是在接口方法中通过@MapKey("stuNo")来指定Map的key; oracle中 stuNo需要全部大写)
<!-- 返回值是HashMap, 并且key是stuNo, value是student -->
<select id="queryStudentsByHashMap1" resultType="HashMap">
select stuNo, stuName, stuAge from student
</select>
// 返回值是HashMap, 并且每个key是stuNo, value是Student
// 这里要注意: Oracle中元数据都是大写(元数据就是字段名, 表名等)
@MapKey("stuNo")
HashMap<Integer, Object> queryStudentsByHashMap1();
- 使用List<\HashMap> 接收返回的多个值, 每个值就对应List的一个元素
<!-- 返回值是HashMap, 但接口的返回值是 List<HashMap<Integer, Object>> -->
<select id="queryStudentsByListHashMap" resultType="HashMap">
select stuNo, stuName, stuAge from student
</select>
// 返回值是 List<HashMap<Integer, Object>>
List<HashMap<Integer, Object>> queryStudentsByListHashMap();
9. resultMap : 字段和属性的对应关系
-
如果属性和字段的名字和类型都能匹配, 就使用 resultType; 否则(就是属性和字段名字或类型不匹配)就使用resultMap指定对应关系; 需要单独配置 resultMap
-
resultMap对应关系示例
<!--
type: 这条查询语句的返回值类型
id(标签内): 唯一标识, 为了让SQL标签的resultMap属性来引用
id(标签体): 用于指定主键的对应关系
result: 用于指定一般字段的对应关系
prpperty: been中的属性名,严格区分大小写
column: 数据表的字段名
jdbcType: 用于指定数据表的类型, 全部大写
javaType: been属性的类型
association: 用于指定一对一查询时另外一张表的对应关系; 下面是立即查询的示例;延迟查询时标签体内不写东西, 使用association标签的select=namespace.id来引入查询语句
select(association标签内): 用来引入一对一查询时另外一张表的字段属性对关系
ofType: 用于指定该属性的元素的类型
collection: 用来指定一对多时的对应关系
-->
<resultMap type="Student" id="query_student_map">
<id property="stuNo" column="sno" />
<result property="stuName" column="sname" />
<result property="stuAge" column="sage" />
<result property="stuSex" column="sex" jdbcType="INTEGER" javaType="boolean"/>
<!-- 一对一时, 对象成员使用 association映射, javaType指定该属性的类型
<association property="studentCard" javaType="StudentCard" select="namespace.id" column="column" ofType="Student">
<id property="cardId" column="cardId"/>
<result property="cardInfo" column="cardInfo"/>
</association>
-->
<!-- 一对多是 collection
在collection的内部还可以接着配 Student 内的 studentCard的信息, 使用的标签是 association
<collection property="students" ofType="Student">
<id property="stuNo" column="stuNo"/>
<result property="stuName" column="stuName"/>
<result property="stuAge" column="stuAge"/>
</collection>
-->
</resultMap>
resultMap 中配置鉴别器 discriminator
-
resultMap 鉴别器: 就是对查询结果进行分支处理; 即可以根据不同的case, 返回不同的值; 如: 若年级是class1, 显示真名, 年级是class2, 显示昵称
- 理解就是: discriminator 完成的就是把不同的数据库表的字段根据某个条件映射到JavaBeen的一个属性上, 从而完成返回的值就是多字段对应一个属性
-
修改数据库以及增加需要的数据
-- 准备sql
alter table student add column nickName varchar(20);
update student set student.nickName = 'zxs' where stuNo = 1;
update student set student.nickName = 'lxs' where stuNo = 2;
update student set student.nickName = 'wxw', graName='class2' where stuNo = 3;
insert into student values(4,'zl',20,'class1',0,'ts','lq',1,1,'zxl');
- 在 mapper.xml 中配置SQL标签及resultMap
<!-- 配置SQL标签以及resultMap -->
<!-- 使用鉴别器: 若年级是class1, 显示真名, 年级是class2, 显示昵称 -->
<resultMap id="query_student_with_disc_map" type="Student">
<!-- 学生的信息 -->
<id property="stuNo" column="stuNo"/>
<result property="stuAge" column="stuAge"/>
<result property="graName" column="graName"/>
<discriminator javaType="String" column="graName">
<case value="class1" resultType="Student">
<result property="stuName" column="stuName" />
</case>
<case value="class2" resultType="Student">
<result property="stuName" column="nickName" />
</case>
</discriminator>
</resultMap>
<select id="queryStudentsWithDisc" resultMap="query_student_with_disc_map">
select stuNo, stuName, graName, nickName from student
</select>
- 接口以及测试类
// 接口方法及测试类
// 使用鉴别器显示stuName属性映射不同的数据库表
List<Student> queryStudentsWithDisc();
// 测试上面接口的方法
public static void queryStudentsWithDisc() throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> students = mapper.queryStudentsWithDisc();
for (Student student : students) {
System.out.println(student);
}
}
/**
* 使用鉴别器的输出结果
* Student{stuNo=1, stuName='zs', stuAge=0, graName='class1'}
* Student{stuNo=2, stuName='lxs', stuAge=0, graName='class2'}
* Student{stuNo=3, stuName='wxw', stuAge=0, graName='class2'}
* Student{stuNo=4, stuName='zl', stuAge=0, graName='class1'}
*
* 未使用鉴别器的输出结果
* Student{stuNo=1, stuName='zs', stuAge=18, graName='class1'}
* Student{stuNo=2, stuName='LS', stuAge=14, graName='class2'}
* Student{stuNo=3, stuName='ww', stuAge=27, graName='class2'}
* Student{stuNo=4, stuName='zl', stuAge=20, graName='class1'}
*/
10. 别名问题
-
给包起别名就是让该包的类可以直接使用不区分大小写的类名
-
如果在批量起别名时,出现了冲突(包和子包含有同名的类), 可以使用@Alias("别名")来再设一个别名来区分
<typeAliases>
<!-- 给xyz.xmcs.Entity包(以及子包)中的所有类起了别名; 别名:不带包名的类名, 并且不区分大小写 -->
<package name="xyz.xmcs.Entity"></package>
</typeAliases>
11. 内置参数
-
_paramter: 代表MyBatis的输入参数值, 可以代替#{}花括号中的值
-
_databaseId: 代表当前数据库的id
12. 模糊查询问题
-
模糊查询的三种方式: 使用${}; 或使用#{}但是传值时传%s%; 或者 bind参数
-
第一种: '%$%': 缺点:不能防止SQL注入; 优点: 传进去的值会原样输出
-
第二种: #, 传入值: %jef% : 优势: 有效防止安全问题
-
第三种: <\bind name="_queryName" value=" '%' + stuName + '%' " />; 通过bind将传入的值进行处理, 加'%'; 也就是将第二种的传值提前到的SQL配置种
13. MyBatis框架及源码了解
MyBatis 框架
-
MyBatis 框架从上到下依次是: 接口层(广义的接口) -> 数据处理曾 -> 框架支撑层 -> 引导层
-
接口层(广义的接口) : 增加接口(方法), 删除, 修改, 查询, 各种直接使用的配置方法
-
数据处理层 : 用于 参数处理, SQL解析, SQL执行, 处理结果, 底层使用 Executor 来执行
-
框架支撑层 : 用于事务管理, 缓存机制, 连接池管理等... (主要是各种辅助功能)
-
引导层 : SQL语句的方式, XML配置, 注解方式
MyBatis 框架源码了解
-
一条SQL执行时, 在MyBatis种的步骤
-
获取 SqlSessionFactory 对象
-
获取 SqlSession 对象
-
获取XxxMapper 对象 (代理接口中的方法, 或mapper.xml中的<\select | update| insert | delete>标签)
-
执行 <\select | update| insert | delete> 等标签中定义的SQL语句
-
-
获取 SqlSessionFactory 对象
-
通过 parseConfiguration()在configuration标签设置了properties, settings, environments 等属性标签, 将所有的配置信息放在了Configuration对象中
-
解析所有的XxxMapper.xml 文件(分析其中的增删改查标签); <\select id="" resultType="" 等标签属性是通过parseStatementNode()解析后构建成MappedStatement对象
- 即: MappedStatement 对象就是 <\select> 这样的标签
-
MappedStatement, environmemt 存在于 Configuration 中; 即: 所有的配置信息, 增删改标签全部存在于 Configuration 中; Configuration 又存在于 DefaultSqlSessionFactory 对象中 (也就是 SqlSessionFactory)
-
总结: SqlSessionFactory对象 -> DefaultSqlSessionFactory -> Configuration -> 包含了一切配置信息
-
-
获取 SqlSession 对象
-
configuration.newExecutor(tx, execType) -> SimpleExecutor
-
MyBatis 根据不同类型execType, 产生不同的Executor, 并且会对执行器进行拦截操作: Executor executor = (Executor)this.interceptorChain.pluginAll(executor);通过装饰模式, 将刚才产生的executor包装成一个更强大的executor
- 作用: 如果要给MyBatis增加自己开发的插件, 就可以通过拦截器实现
-
总结: SqlSession -> openSession() -> openSessionFromDataSource() -> DefaultSqlSession对象
-
-
获取XxxMapper对象, 执行
-
执行增删改查 -> MapperProxy/invoke() --> invocationHandler(JDK提供的动态代理对象)
- 这里用到了动态代理模式: 将增删改查的执行交给了代理对象(MapperProxy对象) -> 代理对象帮我们"代理执行"增删改查方法
-
mapperMethod.execute(SqlSession, args): 实际调用增删改查方法, 依靠了 SqlSession中的 configuration 和 executor
-
处理增删改查方法的参数: method.convertArgsToSqlCommonParam(args); 如果参数是0个, return null; 如果参数是1个, return 第一个, 如果参数是多个, 将参数放入Map中返回
-
查询方法: selectOne() -> selectList() : configuration.getMappedStatement() 即获取到用于增删改查的对象
-
boundSql : 将我们写的SQL和参数值进行拼接后的对象, 这就是我们最后能执行的SQL
-
如果缓存中没有要查询的内容, 则进入数据库真实查询: 调用的是 queryFromDatabase() 方法
-
MyBatis 底层使用的是PreparedStatement的execute()方法执行的, 真正执行SQL的是Executor
-
MyBatis底层在执行CRUD时, 可能会涉及到四个处理器: StatementHandler(处理PreparedStatement的控制器), ParameterHandler(处理参数的控制器), ResultSetHandler(处理结果集的控制器), TypeHandler(类型转换的控制器)
-
XxxMapper:重点就包含三个对象: SqlSession(Configuration, executor,事务), 代理接口的对象(MapperInterface), methodCache(存放查询缓存, 底层是 currentHashMap)
-
13. 自定义插件
-
四大核心对象: StatementHandler, ParameterHandler, ResultSetHandler, Executor
-
四大核心对象, 都涉及了拦截器用于增强
-
四大核心对象都包含了 增强操作
-
-
自定义插件的编写逻辑: 根据MyBatis规则编写一个拦截器, 在拦截器内部加入自定义的增强功能, MyBatis 会自动将该拦截器放入拦截器链
-
编写多个拦截器时, 拦截器的执行顺序和在 <\plugins> 中的配置顺序一致
-
多个拦截器的执行顺序: 调用方法时, 先进入拦截器1的plugin()和setPropertis()方法, 再进入拦截器2的plugin()和setPropertis()方法, 再返回进入拦截器1的interceptor()方法, 再进入拦截器2的interceptor()方法, 最后再执行方法
自定义插件步骤:
- 编写一个类实现 org.apache.ibatis.plugin.Interceptor接口的方法
package xyz.xmcs.my.interceptors;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import java.sql.Statement;
import java.util.Properties;
/**
* @author jefxff
* @date 2020/4/13 - 22:33
*/
@Intercepts({
@Signature(
type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}
)
})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("拦截方法...intercept...");
Object proceed = invocation.proceed(); // 放行
return proceed;
}
@Override // 将拦截器中定义的增强功能和原来的核心对象合并起来, 称为最终的核心对象
public Object plugin(Object o) {
Object wrap = Plugin.wrap(o, this);
return wrap;
}
@Override
public void setProperties(Properties properties) {
System.out.println("设置属性: " + properties);
}
}
- 编写 @Intercepts({@Signature()}) 签名注解
@Intercepts({
@Signature(
type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}
)
})
- 在总配置 conf.xml 中配置
<!-- 配置插件: 在这里配置自定义的插件 -->
<plugins>
<plugin interceptor="xyz.xmcs.my.interceptors.MyInterceptor">
<property name="name" value="zs" />
<property name="age" value="23"/>
</plugin>
</plugins>
编写插件修改SQL语句
-
主要的工作就是在拦截器中拿到SQL的语句和参数, 修改后, 放行
-
在拦截器实现类的intercept方法中, 通过 Object target = invocation.getTarget() 获取目标方法;
-
通过 SystemMetaObject.forObject(target); 获取 MetaObject 类型的元数据
-
通过MetaObject的实例调用setValue(String name, Object value) 来修改
- 重点: name 的值就是 StatementHandler, ParameterHandler, ResultSetHandler, Executor 这四个核心对象的属性
-
修改参数后, 就可以放行了 Object proceed = invocation.proceed(); // 放行
package xyz.xmcs.my.interceptors;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Statement;
import java.util.Properties;
/**
* @author jefxff
* @date 2020/4/13 - 22:33
*/
@Intercepts({
@Signature(
// type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}
// 这里修改了 method 的值为parameterize, 因为是要修改SQL语句的参数
type = StatementHandler.class, method = "parameterize", args = {Statement.class}
)
})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("拦截方法...intercept...");
Object target = invocation.getTarget(); // 目标方法
System.out.println("目标方法: " + target);
MetaObject metaObject = SystemMetaObject.forObject(target);
// Object value = metaObject.getValue("parameterHandler.parameterObject");
// System.out.println(value);
// metaObject.setValue("parameterHandler.parameterObject",2);
// Object value2 = metaObject.getValue("parameterHandler.parameterObject");
// System.out.println(value2);
// 获取SQL语句
Object value1 = metaObject.getValue("parameterHandler.boundSql.sql");
// 修改SQL语句
//metaObject.setValue("parameterHandler.boundSql.sql", "select stuName, stuAge from student where stuNo = ?");
// 获取参数
Object value = metaObject.getValue("parameterHandler.boundSql.parameterObject");
System.out.println(value);
// 修改参数
metaObject.setValue("parameterHandler.boundSql.parameterObject", 2);
Object proceed = invocation.proceed(); // 放行
return proceed;
}
@Override // 将拦截器中定义的增强功能和原来的核心对象合并起来, 称为最终的核心对象
public Object plugin(Object o) {
Object wrap = Plugin.wrap(o, this);
// System.out.println("plugin... " + wrap);
return wrap;
}
@Override
public void setProperties(Properties properties) {
System.out.println("设置属性: " + properties);
}
}
14. 批量增删改(DDl)
-
批量操作就记住, 在获取SqlSessionFactory对象是使用sessionFactory.openSession(ExecuteType.BATCH), 就可以执行批量增删改; openSession() 形参为空默认就是SIMPLE
-
使用BATCH之后, SQL预编译一次, 其余次数只需要设置参数值即可; 不使用BATCH, 预编译N次, 每次DML都需要执行完整的SQL
-
其他形式的批量增删改(一个目的, 拼接SQL语句)
-
create table 表 select ... from 旧表;
-
insert into 表(...) select ... from 表;
-
begin ...(DML)... end;
-
(Oracle) 数据泵, SQL Loader, 外部表
-
-
MySQL 批量插入的例子(了解, 最好不要使用)
-
没有用到MyBatis的BATCH批量插入
-
这种写法不适合数据库迁移
-
<!-- 批量增加数据, MySQL -->
<insert id="insertStudentMySQL" >
<foreach collection="list" item="stu" open="insert into student(stuNo, stuName) values " close=";" separator=", ">
(#{stu.stuNo}, #{stu.stuName})
</foreach>
</insert>
// 事务控制批量增加
int insertStudentMySQL(List<Student> students);
//
// 批量增加学生: MySQL
public static void insertStudentMySQL() throws IOException {
SqlSession sqlSession = getSqlSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student stu1 = new Student(1001,"zs1");
Student stu2 = new Student(1002,"zs2");
Student stu3 = new Student(1003,"zs3");
Student stu4 = new Student(1004,"zs4");
Student stu5 = new Student(1005,"zs5");
List<Student> students = new ArrayList<>();
students.add(stu1);
students.add(stu2);
students.add(stu3);
students.add(stu4);
students.add(stu5);
int i = mapper.insertStudentMySQL(students);
System.out.println("插入了 [" + i + "] 条数据");
sqlSession.commit();
sqlSession.close();
}
15. 使用pageHelper插件
-
下载两个jar包, pagehelper-5.0.1.jar, jsqlparser-0.9.5.jar
-
在MyBatis总配置中配置plugin
<!-- 配置插件: 在这里配置自定义的插件 -->
<plugins>
<!-- 配置分页插件 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>
</plugins>
- 使用
public static void queryAllStudent() throws IOException {
SqlSession sqlSession = getSqlSession();
// 下面两句代码是使用动态代理方式
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
// 这是第一种方法, 直接调用方法startPage(), 返回值是Page类型的, 里面包含了Student数据, 以及分页需要的数据
// Page<Student> page = PageHelper.startPage(1, 10);
// List<Student> studentss = studentMapper.queryAllStudent();
// 这是第二种方法, 使用Lambda, 相当于上面第一种方法的两句代码; 返回值是Page类型的, 里面包含了Student数据, 以及分页需要的数据
Page<Student> page = PageHelper.startPage(1, 10).doSelectPage(studentMapper::queryAllStudent); // 这是第二种方法
// pageInfo 所包含的值(或功能)比pageInfo更多
PageInfo<Student> pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(studentMapper::queryAllStudent);
int nextPage = pageInfo.getNextPage();
System.out.println("nextPage: " + nextPage);
List<Student> list = page.getResult();
System.out.println(page);
System.out.println("当前页: " + page.getPageNum());
System.out.println("总数据量: " + page.getTotal());
System.out.println("总页码: " + page.getPages());
System.out.println("页面大小: " + page.getPageSize());
for (Student student : list) {
System.out.println(student);
}
sqlSession.commit();
sqlSession.close();
}