目录
一:使⽤javassist⽣成类
Javassist是⼀个开源的分析、编辑和创建Java字节码的类库。是由东京⼯业⼤学的数学和计算机科学 系的 Shigeru Chiba (千叶 滋)所创建的。它已加⼊了开放源代码JBoss 应⽤服务器项⽬,通过使⽤ Javassist对字节码操作为JBoss实现动态"AOP"框架(面向切面编程)。
1. Javassist的使⽤
(1)引入javassist依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bjpowernode</groupId>
<artifactId>javassist-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--javassist依赖-->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
(2)编写测试类
①先调用ClassPool.getDefault()方法获取类池pool,通过这个类池用来生成class;
②调用类池pool的makeClass方法制造类,参数用来指定类名;
③有了类就可以调用CtMethod.make方法制造方法,第一个参数是定义的方法,第二个参数是制造的类;
④调用制造类的addMethod方法,将方法添加到类中;
⑤调用制造类的toClass()方法,在内存中生成class;
⑥通过反射机制,调用方法;
package com.bjpowernode.javassist;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javassist
* @Project:mybatis
* @name:JavassistTest
* @Date:2023/1/2 14:38
*/
public class JavassistTest {
@Test
public void testGenerateFirstClass() throws Exception {
// 获取类池,这个类池就是用来生成class的
ClassPool pool = ClassPool.getDefault();
// 制造类(告诉javassist类名是啥)
CtClass ctClass = pool.makeClass("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
// 制造方法
String methodCode = "public void insert(){System.out.println(123);}";
CtMethod ctMethod = CtMethod.make(methodCode, ctClass);
// 将方法添加到类中
ctClass.addMethod(ctMethod);
// 在内存中生成class
ctClass.toClass();
// 调用方法,以下都是反射机制的知识点
// 类加载到JVM当中,返回AccountDaoImpl的字节码
Class<?> clazz = Class.forName("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
// 创建对象
Object obj = clazz.newInstance();
// 获取AccountDaoImpl中的insert方法
Method insertMethod = clazz.getDeclaredMethod("insert");
// 调用obj的insert方法
insertMethod.invoke(obj);
}
}
JDK8之后运⾏要注意:加两个参数,要不然会有异常。
①--add-opens java.base/java.lang=ALL-UNNAMED
②--add-opens java.base/sun.net.util=ALL-UNNAMED
2. 动态生成类并实现接口
(1)先定义一个接口
package com.bjpowernode.ban.dao;
public interface AccountDao {
void delete();
}
(2)使用javassist动态生成类并实现接口,和上面的代码很类似,主要是在添加方法之前,制造上面的接口到内存当中,然后把这个接口添加到类当中即可。
注意1:ctClass.addInterface(ctInterface);把接口添加都类当中,实际上就等价于AccountDaoImpl implements AccountDao;
注意2:ctClass.toClass()实际上有两个功能:一是在内存中生成类,二是同时将生成的类加载到JVM当中;
package com.bjpowernode.javassist;
import com.bjpowernode.ban.dao.AccountDao;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javassist
* @Project:mybatis
* @name:JavassistTest
* @Date:2023/1/2 14:38
*/
public class JavassistTest {
@Test
public void testGenerateImpl() throws Exception{
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
// 制造接口
CtClass ctInterface = pool.makeInterface("com.bjpowernode.ban.dao.AccountDao");
// 添加接口到类中
ctClass.addInterface(ctInterface);
// 实现接口中的方法
// 制造方法
CtMethod ctMethod = CtMethod.make("public void delete(){System.out.println(\"Hello World\");}", ctClass);
// 将方法添加到类中
ctClass.addMethod(ctMethod);
// 在内存中生成类,同时将生成的类加载到JVM当中
Class clazz = ctClass.toClass(); // 等价于上面的两行代码
AccountDao accountDao = (AccountDao) clazz.newInstance(); // 强转,面向接口编程
accountDao.delete();
}
}
(3)上面我们是已知接口中有一个类,所以在制造方法时写死了;但如果不知道接口中有多少个方法呢?怎么实现接口中所有的方法?
①接口中有多个方法
package com.bjpowernode.ban.dao;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.ban.dao
* @Project:mybatis
* @name:AccountDao
* @Date:2023/1/2 15:36
*/
public interface AccountDao {
void delete();
int insert(String actno);
int update(String actno,Double balance);
String selectByActno(String actno);
}
②动态的实现所有的方法,主要利用反射机制,进行代码的拼接
package com.bjpowernode.javassist;
import com.bjpowernode.ban.dao.AccountDao;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import org.junit.Test;
import java.lang.reflect.Method;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.javassist
* @Project:mybatis
* @name:JavassistTest
* @Date:2023/1/2 14:38
*/
public class JavassistTest {
@Test
public void testGenerateAccountDaoImpl() throws Exception {
// 获取类池
ClassPool pool = ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass("com.bjpowernode.bank.dao.impl.AccountDaoImpl");
// 制造接口
CtClass ctInterface = pool.makeInterface("com.bjpowernode.ban.dao.AccountDao");
// 实现接口
ctClass.addInterface(ctInterface);
// 实现接口中所有的方法
// 获取接口中所有的方法
Method[] methods = AccountDao.class.getDeclaredMethods();
for (Method method:methods){
// 把抽象方法实现了
CtMethod ctMethod = null;
try {
// 拼出methodCode的方法
StringBuilder methodCode = new StringBuilder();
methodCode.append("public "); // 追加修饰符列表
methodCode.append(method.getReturnType().getSimpleName()); // 追加返回值类型
methodCode.append(" ");
methodCode.append(method.getName()); // 追加方法名
methodCode.append("(");
// 拼接参数类型
Class<?>[] parameterTypes = method.getParameterTypes(); // 获取到所有的参数类型
for (int i = 0; i <parameterTypes.length ; i++) { // 遍历这个数组
// 遍历,取出每个进行遍历拼接
Class<?> parameterType = parameterTypes[i];
methodCode.append(parameterType.getSimpleName()); // 拼接参数类型
methodCode.append(" ");
methodCode.append("arg"+i); // 拼接变量名,使用下标防止变量名冲突
if (i != parameterTypes.length-1){ // 如果不是最后一个参数,就加上逗号
methodCode.append(",");
}
}
methodCode.append("){System.out.println(1111);");
// 添加返回值类型 return语句
String returnTypeSimpleName = method.getReturnType().getSimpleName();
if ("void".equals(returnTypeSimpleName)) {
// 什么都不做
}else if ("int".equals(returnTypeSimpleName)){
methodCode.append("return 1;");
}else if("String".equals(returnTypeSimpleName)){
methodCode.append("return \"Hello\";");
}
methodCode.append("}");
// System.out.println(methodCode);
ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
// 在内存中生成class,并且加载到JVM当中
Class clazz = ctClass.toClass();
// 创建对象
AccountDao accountDao = (AccountDao) clazz.newInstance();
// 调用方法
accountDao.insert("111");
accountDao.delete();
accountDao.update("111",1000.0);
accountDao.selectByActno("111");
}
}
(4)所以对于AccountDao的实现类AccountDaoImpl就不用手动写了,利用javassist编写一个工具类GenerateDaoProxy,用来动态生成AccountDaoImpl。
注意:实际上Mybatis已经内置了javassist,直接使用即可,不需要在引入javassist依赖。
重点:sql语句的id是框架使用者提供的,具有多变性。对于框架的开发人员来说,不知道。
既然框架开发者不知道sqlId,怎么办呢?mybatis框架的开发者于是就出台了一个规定:凡是使用GenerateDaoProxy机制的:sqlId都不能随便写,namespace必须是dao接口的全限定名称,id必须是dao接口中方法名。
①要动态生成的类
package com.bjpowernode.bank.bao.impl;
import com.bjpowernode.bank.bao.AccountDao;
import com.bjpowernode.bank.pojo.Account;
import com.bjpowernode.bank.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
public class AccountDaoImpl implements AccountDao {
@Override
public Account selectByActno(String actno) {
SqlSession sqlSession = SqlSessionUtils.openSession();
/* Account account = (Account) sqlSession.selectOne("account.selectByActno", actno);
return account;*/
return (Account) sqlSession.selectOne("account.selectByActno", actno);
}
@Override
public int updateByActno(Account act) {
SqlSession sqlSession = SqlSessionUtils.openSession();
/*int count = sqlSession.update("account.updateByActno", act);
return count;*/
return sqlSession.update("account.updateByActno", act);
}
}
②修改专门编写sql语句的AccountMapper.xml文件:namespace修改为dao接口的全限定名称,id修改为dao接口中方法名
<?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.bjpowernode.bank.bao.AccountDao">
<select id="selectByActno" resultType="com.bjpowernode.bank.pojo.Account">
select * from t_act where actno=#{actno}
</select>
<update id="updateByActno">
update t_act set balance = #{balance} where actno = #{actno};
</update>
</mapper>
③ GenerateDaoProxy工具类,专门用来动态实现Dao接口的实现类的
package com.bjpowernode.bank.utils;
import org.apache.ibatis.javassist.CannotCompileException;
import org.apache.ibatis.javassist.ClassPool;
import org.apache.ibatis.javassist.CtClass;
import org.apache.ibatis.javassist.CtMethod;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.session.SqlSession;
import java.lang.reflect.Method;
/**
* 工具类:可以动态的生成DAO的实现类
* @Author:朗朗乾坤
* @Package:com.bjpowernode.bank.utils
* @Project:mybatis
* @name:GenerateDaoProxy
* @Date:2023/1/2 19:42
*/
public class GenerateDaoProxy {
/**
* 生成dao接口的实现类,并且将实现类的对象创建出来并返回
* @param daoInterface
* @return dao接口实现类的实例化对象
*/
public static Object generate(SqlSession sqlSession,Class daoInterface){ // 参数传一个接口
// 类池
ClassPool pool = ClassPool.getDefault();
// 制造类
CtClass ctClass = pool.makeClass(daoInterface.getName() + "Impl"); // 实际本质上就是在内存中动态生成一个代理类
// 制造接口
CtClass ctInterface = pool.makeInterface(daoInterface.getName());
// 实现接口
ctClass.addInterface(ctInterface);
// 实现接口中的方法
Method[] methods = daoInterface.getDeclaredMethods();
for (Method method : methods){
// 拼方法
StringBuilder methodCode = new StringBuilder();
methodCode.append("public");
methodCode.append(method.getReturnType().getName());
methodCode.append(" ");
methodCode.append(method.getName());
methodCode.append("(");
// 方法的形式参数列表
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
methodCode.append(parameterType.getName());
methodCode.append(" ");
methodCode.append("arg"+i);
if (i != parameterTypes.length-1){
methodCode.append(",");
}
}
methodCode.append(")");
methodCode.append("{");
// 需要方法体当中的代码
// 第一行代码是都相同的,注意要带上包名
methodCode.append("org.apache.ibatis.session.SqlSession sqlSession = com.bjpowernode.bank.utils.SqlSessionUtils.openSession();");
// 第二行代码要先需要知道是什么类型的sql语句
// SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sql语句的id).getSqlCommandType();
// 现在关键问题就是如何获得sql语句的id?
// 获取sqlId(这⾥⾮常重要:因为这⾏代码导致以后namespace必须是接⼝的全
// 限定接⼝名,sqlId必须是接⼝中⽅法的⽅法名。)
String sqlId = daoInterface.getName() + "." + method.getName();
SqlCommandType sqlCommandType = sqlSession.getConfiguration().getMappedStatement(sqlId).getSqlCommandType();
if (sqlCommandType == SqlCommandType.INSERT) {
}
if (sqlCommandType == SqlCommandType.DELETE) {
}
if (sqlCommandType == SqlCommandType.UPDATE) {
// 变量名上面我们拼接用的是arg加下标,这里应该是arg0
methodCode.append("return sqlSession.update(\""+sqlId+"\", arg0);");
}
if (sqlCommandType == SqlCommandType.SELECT) {
// 动态获取强转的类型
String returnType = method.getReturnType().getName();
methodCode.append("return ("+returnType+")sqlSession.selectOne(\""+sqlId+"\", arg0);");
}
methodCode.append("}");
try {
CtMethod ctMethod = CtMethod.make(methodCode.toString(), ctClass);
ctClass.addMethod(ctMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
// 创建对象
Object obj = null;
try {
Class<?> clazz = ctClass.toClass();
obj = clazz.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
④Service调用实现类,原来是实现了 AccountDaoImpl类,Service通过new的方式创建;现在是通过上面的工具类 GenerateDaoProxy来创建对象
3. MyBatis中接⼝代理机制及使⽤
(1)幸运的是对于GenerateDaoProxy功能的实现,Mybatis框架已经封装好了,在Mybatis当中实际上采用了代理模式;在内存中生成dao接口的代理类,然后创建代理类的实例。
(2)使用Mybatis的这种代理机制的前提:SqlMapper.xml文件中namespace必须是dao接口的全限定名称,id必须是dao接口中的方法名。
(3)怎么用?代码怎么写?调用sqlSession对象的getMapper方法AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
二:面向接口的方式进行CRUD
(1)既然已经学习了面向接口编程和使⽤javassist⽣成类,下面就尝试面向接口的方式进行CRUD操作。
(2)以后重点写映射文件CarMapper.xml和接口CarMapper就可以。
框架结构
(1)pom.xml中引入依赖
jdbc.properties连接数据库的配置文件、mybatis-config.xml核心配置文件、logback日志配置文件都和前面相同,这里就不在列出。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bjpowernode</groupId>
<artifactId>mybatis-005-crud2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!--引入依赖-->
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.23</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!--logback依赖-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
</project>
(2)接口CarMapper,在这个接口中写抽象方法,下面用动态代理机制生成实现类
package com.bjpowernode.mybatis.mapper;
import com.bjpowernode.mybatis.pojo.Car;
import java.util.*;
public interface CarMapper { // 就相当于CarMapper
// 增
int insert(Car car);
// 删
int deleteById(Long id);
// 改
int update(Car car);
// 查一个
Car selectById(Long id);
// 查所有
List<Car> selectAll();
}
(3)CarMapper.xml映射文件,编写sql语句
<?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.bjpowernode.mybatis.mapper.CarMapper">
<!--id都是接口中的方法-->
<insert id="insert">
insert into t_car values(null, #{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
<delete id="deleteById">
delete from t_car where id = #{id}
</delete>
<update id="update">
update t_car set
car_num=#{carNum},
brand=#{brand},
guide_price=#{guidePrice},
produce_time=#{produceTime},
car_type=#{carType}
where id = #{id}
</update>
<select id="selectById" resultType="com.bjpowernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car where id = #{id}
</select>
<select id="selectAll" resultType="com.bjpowernode.mybatis.pojo.Car">
select
id,
car_num as carNum,
brand,
guide_price as guidePrice,
produce_time as produceTime,
car_type as carType
from t_car
</select>
</mapper>
(4)测试类,使用动态代理机制实现CRUD
package com.bjpowernode.mybatis.test;
import com.bjpowernode.mybatis.mapper.CarMapper;
import com.bjpowernode.mybatis.pojo.Car;
import com.bjpowernode.mybatis.utils.SqlSessionUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.*;
/**
* @Author:朗朗乾坤
* @Package:com.bjpowernode.mybatis.test
* @Project:mybatis
* @name:CarMapperTest
* @Date:2023/1/3 12:10
*/
public class CarMapperTest {
@Test
public void testInsert(){
SqlSession sqlSession = SqlSessionUtils.openSession();
// 面向接口获取接口的代理对象
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(null, "4444", "奔驰C200", 32.0, "2000-10-10", "新能源");
int count = mapper.insert(car);
System.out.println(count);
sqlSession.commit();
}
@Test
public void testDeleteById(){
SqlSession sqlSession = SqlSessionUtils.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
int count = mapper.deleteById(15L);
System.out.println(count);
sqlSession.commit();
}
@Test
public void testUpdate(){
SqlSession sqlSession = SqlSessionUtils.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = new Car(19L, "2222", "凯美瑞222", 3.0, "2000-10-10", "新能源");
mapper.update(car);
sqlSession.commit();
}
@Test
public void testSelectById(){
SqlSession sqlSession = SqlSessionUtils.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
Car car = mapper.selectById(20L);
System.out.println(car);
}
@Test
public void testSelectAll(){
SqlSession sqlSession = SqlSessionUtils.openSession();
CarMapper mapper = sqlSession.getMapper(CarMapper.class);
List<Car> cars = mapper.selectAll();
for (Car car : cars){
System.out.println(car);
}
}
}