封装JDBC,实现简单ORM框架
本文将封装JDBC的操作,实现简单的ORM框架,提供3种风格的api来给用户使用(1.原生jdbc+SqlBuilder;2.类似jpa和mp的;3.注解+接口方法)
代码仓库:malred/IFullORM
1. 原生JDBC+sql构建器
第一步: 封装jdbc
这个框架的重点是将jdbc等操作隐藏到框架内部,从而让用户更轻松地实现sql操作,JDBC的部分就粗略地过一下代码
JDBCUtils
package org.malred.utils;import java.sql.*;public class JDBCUtils {static String url;// defaultUrl+dbName -> urlstatic String defaultUrl = "jdbc:mysql://localhost:3306/";static String dbName;static String driverName;static String defaultDriverName = "com.mysql.cj.jdbc.Driver";static String user;static String password;public static void setDataSource(String url, String driverName, String user, String password) {JDBCUtils.url = url;JDBCUtils.driverName = driverName;JDBCUtils.user = user;JDBCUtils.password = password;}public static void setDataSource(String dbName, String user, String password) {JDBCUtils.url = defaultUrl + dbName;JDBCUtils.driverName = defaultDriverName;JDBCUtils.user = user;JDBCUtils.password = password;}public static void setUrl(String url) {JDBCUtils.url = url;}public static void setDriverName(String driverName) {JDBCUtils.driverName = driverName;}public static void setUser(String user) {JDBCUtils.user = user;}public static void setPassword(String password) {JDBCUtils.password = password;}public static Connection getConn() {try {// 四要素 -> 让用户传
// String url = "jdbc:mysql://localhost:3306/test";
// String user = "root";
// String password = "root";
// String driverName = "com.mysql.cj.jdbc.Driver";//实例化驱动Class.forName(driverName);//获取连接Connection conn = DriverManager.getConnection(url, user, password);return conn;} catch (Exception e) {e.printStackTrace();return null;}}public static void close(Connection conn, PreparedStatement ps) {try {if (conn != null) conn.close();if (ps != null) ps.close();} catch (SQLException e) {e.printStackTrace();}}public static void close(Connection conn) {try {if (conn != null) conn.close();} catch (SQLException e) {e.printStackTrace();}}public static void close(PreparedStatement ps) {try {if (ps != null) ps.close();} catch (SQLException e) {e.printStackTrace();}}public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {try {if (conn != null) conn.close();if (ps != null) ps.close();if (rs != null) rs.close();} catch (Exception e) {e.printStackTrace();}}
}
Operate
该部分代码来源于:JDBC(3)实现通用的增删改查方法_如何将增删改查方法抽成公共方法-CSDN博客
public class Operate {//通用的更新数据库的方法:insert,update,delete 语句时public static int update(String sql) throws SQLException {//1、获取连接Connection conn = JDBCUtils.getConn();//2、获取 Statement 对象,这个对象是用来给服务器传 sql 并执行 sqlStatement st = conn.createStatement();//3、执行 sqlint len = st.executeUpdate(sql);//4、释放资源JDBCUtils.close(conn, (PreparedStatement) st);return len;}// 通用的更新数据库的方法:insert,update,delete 语句,允许 sql 带?public static int update(String sql, Object... args) throws SQLException {Connection conn = JDBCUtils.getConn();int len = update(conn, sql, args);JDBCUtils.close(conn);return len;}// 通用的更新数据库的方法:insert,update,delete 语句,允许 sql 带?public static int update(Connection conn, String sql, Object... args) throws SQLException {//2、获取 PreparedStatement 对象,这个对象是用来 sql 进行预编译PreparedStatement pst = conn.prepareStatement(sql);//3、设置 sql 中的?if (args != null && args.length > 0) {//数组的下标是从 0 开始,?的编号是 1 开始for (int i = 0; i < args.length; i++) {pst.setObject(i + 1, args[i]);}}//4、执行 sqlint len = pst.executeUpdate();//5、释放资源JDBCUtils.close(pst);return len;}//通用的查询方法之一:查询一行,即一个对象/*** 执行查询操作的 SQL 语句,SQL 可以带参数(?)** @param clazz Class 查询的结果需要封装的实体的 Class 类型,例如:学生 Student,商品 Goods,订单 Order* @param sql String 执行查询操作的 SQL 语句* @param args Object... 对应的每个?设置的值,顺序要与?对应* @return T 封装了查询结果的实体* @throws Exception*/public static <T> T get(Class<T> clazz, String sql, Object... args) throws Exception {//1、注册驱动//2、获取连接Connection conn = JDBCUtils.getConn();//3、对 sql 进行预编译PreparedStatement pst = conn.prepareStatement(sql);//4、设置?if (args != null && args.length > 0) {//数组的下标是从 0 开始,?的编号是 1 开始for (int i = 0; i < args.length; i++) {pst.setObject(i + 1, args[i]);}}//5、查询ResultSet rs = pst.executeQuery();//获取查询的结果集的元数据信息ResultSetMetaData rsmd = rs.getMetaData();//这是查询的结果集中,一共有几列int count = rsmd.getColumnCount();T t = clazz.newInstance();//要求这个 Javabean 类型必须有无参构造while (rs.next()) {/** 问题?* (1)sql 语句中查询了几列,每一列是什么属性* (2)怎么把这个值设置到 Javabean 的属性中*///循环每一行有几列for (int i = 0; i < count; i++) {//第几列的名称// String columnName = rsmd.getColumnName(i+1);//如果 sql 中没有取别名,那么就是列名,如果有别名,返回的是别名String fieldName = rsmd.getColumnLabel(i + 1);//该列的值// Object value = rs.getObject(columnName);Object value = rs.getObject(fieldName);//设置 obj 对象的某个属性中Field field = clazz.getDeclaredField(fieldName);//JavaBean 的属性名field.setAccessible(true);field.set(t, value);}}//5、释放资源JDBCUtils.close(conn, pst, rs);return t;}//通用的查询方法之二:查询多行,即多个对象//Class<T> clazz:用来创建实例对象,获取对象的属性,并设置属性值/*** 执行查询操作的 SQL 语句,SQL 可以带参数(?)** @param clazz Class 查询的结果需要封装的实体的 Class 类型,例如:学生 Student,商品 Goods,订单 Order* @param sql String 执行查询操作的 SQL 语句* @param args Object... 对应的每个?设置的值,顺序要与?对应* @return ArrayList<T> 封装了查询结果的集合* @throws Exception*/public static <T> ArrayList<T> getList(Class<T> clazz, String sql, Object... args) throws Exception {//1、注册驱动,不用了//2、获取连接Connection conn = JDBCUtils.getConn();//3、对 sql 进行预编译PreparedStatement pst = conn.prepareStatement(sql);//4、对?进行设置值if (args != null && args.length > 0) {for (int i = 0; i < args.length; i++) {pst.setObject(i + 1, args[i]);}}//5、执行 sqlResultSet rs = pst.executeQuery();//获取结果集的元数据ResultSetMetaData metaData = rs.getMetaData();//获取结果中总列数int count = metaData.getColumnCount();//创建集合对象ArrayList<T> list = new ArrayList<T>();while (rs.next()) {//遍历的行//1、每一行是一个对象T obj = clazz.newInstance();//2、每一行有很多列//for 的作用是为 obj 对象的每一个属性设置值for (int i = 0; i < count; i++) {//(1)每一列的名称String fieldName = metaData.getColumnLabel(i + 1);//获取第几列的名称,如果有别名获取别名,如果没有别名获取列名//(2)每一列的值Object value = rs.getObject(i + 1);//获取第几列的值//(3)获取属性对象Field field = clazz.getDeclaredField(fieldName);//(4)设置可见性field.setAccessible(true);//(5)设置属性值field.set(obj, value);}//3、把 obj 对象放到集合中list.add(obj);}//6、释放资源JDBCUtils.close(conn, pst, rs);//7、返回结果return list;}//通用的查询方法之三:查询单个值//单值:select max(salary) from employee; 一行一列//select count(*) from t_goods; 一共几件商品public static Object getValue(String sql, Object... args) throws Exception {//2、获取连接Connection conn = JDBCUtils.getConn();//3、对 sql 进行预编译PreparedStatement pst = conn.prepareStatement(sql);//4、对?进行设置值if (args != null && args.length > 0) {for (int i = 0; i < args.length; i++) {pst.setObject(i + 1, args[i]);}}//5、执行 sqlResultSet rs = pst.executeQuery();Object value = null;if (rs.next()) {//一行value = rs.getObject(1);//一列}//6、释放资源JDBCUtils.close(conn, pst, rs);return value;}
}
第二步 sql构建器 SqlBuilder 类
我们将sql语句拆分为小段,然后定义单独的方法,一个一个地拼接sql语句(链式调用),最后通过一个方法获取类内部存放的sql语句
类的一些字段
public class SqlBuilder {String sql;String tbName;// 表名String joinTb;// 连接的表的名称private SqlBuilder() {}public static SqlBuilder build() {return new SqlBuilder();}public String sql() {return this.sql;}// 自定义基础sqlpublic SqlBuilder base(String U_sql) {this.sql = U_sql;return this;}public SqlBuilder tbName(String tbName) {this.tbName = tbName;return this;}
}
select
select可以拆分为 'select x,x,x from tbName ' + 'join joinTb' + ' on tbName.x=joinTb.x ' + 'where tbName.x=?'
为?的部分由用户调用原生接口时传入参数,jdbc填充,tbName和joinTb也需要用户传入,传入后在构建器内部保存这两个值,x是用户构建sql时要传入的,代表参数(字段)的名称,会写死在sql里
public SqlBuilder select(String tbName, String... columns) {this.sql = "select ";for (int i = 0; i < columns.length; i++) {if (i == columns.length - 1) {this.sql += columns[i] + " ";break;}this.sql += columns[i] + ", ";}this.sql += " from " + tbName;return this;}public SqlBuilder select(String[] columns) {this.sql = "select ";for (int i = 0; i < columns.length; i++) {if (i == columns.length - 1) {this.sql += columns[i] + " ";break;}this.sql += columns[i] + ", ";}this.sql += " from " + tbName;return this;}public SqlBuilder select(String tbName) {this.sql = "select * from " + tbName;return this;}public SqlBuilder select() {this.sql = "select * from " + tbName;return this;}public SqlBuilder join(SqlJoinType type, String joinTb) {this.joinTb = joinTb;sql += " " + Common.JOIN_TYPE[type.ordinal()] + " join " + joinTb;return this;}public SqlBuilder on(String in_column, SqlCompareIdentity identity, String out_column) {sql += " on " + joinTb + "." + in_column +Common.Compares[identity.ordinal()]+ tbName + "." + out_column;return this;}public SqlBuilder count(String tbName) {this.sql = "select count(*) from " + tbName;return this;}public SqlBuilder count() {this.sql = "select count(*) from " + tbName;return this;}public SqlBuilder where(String column, SqlCompareIdentity join) {if (!sql.contains("where")) {this.sql += " where " + column + Common.Compares[join.ordinal()] + " ? ";return this;}this.sql += " and " + column + Common.Compares[join.ordinal()] + "? ";return this;}
update
update可以拆分为 'update tbName set x=?,x=?' + 'where x=?'
public SqlBuilder update(String tbName, String... columns) {this.sql = "update " + tbName + " set ";for (int i = 0; i < columns.length; i++) {if (i == columns.length - 1) {this.sql += columns[i] + "=? ";break;}this.sql += columns[i] + "=?,";}return this;}public SqlBuilder update(String[] columns) {this.sql = "update " + tbName + " set ";for (int i = 0; i < columns.length; i++) {if (i == columns.length - 1) {this.sql += columns[i] + "=? ";break;}this.sql += columns[i] + "=?,";}return this;}
delete
delete => 'delete from tbName' + 'where x= ?'
public SqlBuilder delete(String tbName) {sql = "delete from " + tbName;return this;}public SqlBuilder delete() {sql = "delete from " + tbName;return this;}
insert
insert => 'insert into tbName(x,x,x) values (?,?,?), (?,?,?)'
public SqlBuilder insert(String tbName, String... params) {sql = "insert into " + tbName;sql += "(";for (int i = 0; i < params.length; i++) {if (i == params.length - 1) {sql += params[i] + ") ";break;}sql += params[i] + ",";}sql += "values (";for (int i = 0; i < params.length; i++) {if (i == params.length - 1) {sql += "?)";break;}sql += "?,";}return this;}public SqlBuilder insert(String[] params) {sql = "insert into " + tbName;sql += "(";for (int i = 0; i < params.length; i++) {if (i == params.length - 1) {sql += params[i] + ") ";break;}sql += params[i] + ",";}sql += "values (";for (int i = 0; i < params.length; i++) {if (i == params.length - 1) {sql += "?)";break;}sql += "?,";}return this;}
测试sqlBuilder
public class t {@Beforepublic void before() {// 设置数据库属性JDBCUtils.setDataSource("jdbc:mysql://localhost:3306/test","com.mysql.cj.jdbc.Driver", "root", "root");}// sqlbuilder+jdbc封装@Testpublic void testSelectBuild() {String sql = SqlBuilder.build().select("tb_user").where("id", SqlCompareIdentity.NE).sql();System.out.println(sql);String sql_cols = SqlBuilder.build().select("tb_user", "username", "gender", "addr").where("id", SqlCompareIdentity.NE).sql();System.out.println(sql_cols);String sql_cols_option2 = SqlBuilder.build().select("tb_user", "username", "gender", "addr").where("id", SqlCompareIdentity.NE).where("password", SqlCompareIdentity.NE).sql();System.out.println(sql_cols_option2);String sql_count = SqlBuilder.build().count("tb_user").where("id", SqlCompareIdentity.GT).sql();System.out.println(sql_count);}@Testpublic void testSelectBuildTbName() {// 直接设置build的tbName,然后使用不需要tbName的方法来构建String sql1 = SqlBuilder.build().tbName("tb_user").select().where("id", SqlCompareIdentity.NE).sql();System.out.println(sql1);String sql_cols1 = SqlBuilder.build().tbName("tb_user").select(new String[]{"username", "gender", "addr"}).where("id", SqlCompareIdentity.NE).sql();System.out.println(sql_cols1);String sql_cols_option21 = SqlBuilder.build().tbName("tb_user").select(new String[]{"username", "gender", "addr"}).where("id", SqlCompareIdentity.NE).where("password", SqlCompareIdentity.NE).sql();System.out.println(sql_cols_option21);String sql_count1 = SqlBuilder.build().tbName("tb_user").count().where("id", SqlCompareIdentity.GT).sql();System.out.println(sql_count1);String sql_join = SqlBuilder.build().tbName("tb_user").select().join(SqlJoinType.INNER, "tb_product").on("id", SqlCompareIdentity.EQ, "id").where("tb_user.id", SqlCompareIdentity.EQ).sql();System.out.println(sql_join);}@Testpublic void testUpdateBuild() {String sql = SqlBuilder.build().update("tb_user", "password", "username").where("id", SqlCompareIdentity.EQ).sql();System.out.println(sql);String sql_no_tbname = SqlBuilder.build().tbName("tb_user").update(new String[]{"password", "username"}).where("id", SqlCompareIdentity.EQ).sql();System.out.println(sql_no_tbname);}@Testpublic void testInsertBuild() {String sql = SqlBuilder.build().insert("tb_user", "username", "password", "addr", "gender").sql();System.out.println(sql);}@Testpublic void testInsertBuildTbName() {String sql = SqlBuilder.build().tbName("tb_user").insert(new String[]{"username", "password", "addr", "gender"}).sql();System.out.println(sql);}@Testpublic void testDeleteBuild() {String sql_tb = SqlBuilder.build().tbName("tb_user").delete().where("id", SqlCompareIdentity.EQ).sql();System.out.println(sql_tb);String sql = SqlBuilder.build().delete("tb_user").where("id", SqlCompareIdentity.EQ).sql();System.out.println(sql);}
}
第三步 使用原生接口+sql构建器执行crud
@Beforepublic void before() {// 设置数据库属性JDBCUtils.setDataSource("jdbc:mysql://localhost:3306/test","com.mysql.cj.jdbc.Driver", "root", "root");}@Testpublic void testSelectMulti() throws Exception {ArrayList<TbUser> list;String sql = SqlBuilder.build().select("tb_user").where("id", SqlCompareIdentity.NE).sql();list = Operate.getList(TbUser.class, sql, 1);System.out.println(list);String sql_cols = SqlBuilder.build().select("tb_user", "username", "gender", "addr").where("id", SqlCompareIdentity.NE).sql();list = Operate.getList(TbUser.class, sql_cols, 1);System.out.println(list);String sql_cols_option2 = SqlBuilder.build().select("tb_user", "username", "gender", "addr").where("id", SqlCompareIdentity.NE).where("gender", SqlCompareIdentity.NE).sql();list = Operate.getList(TbUser.class, sql_cols_option2, 1, "男");System.out.println(list);String sql_count = SqlBuilder.build().count("tb_user").where("id", SqlCompareIdentity.GT).sql();Long cnt = (Long) Operate.getValue(sql_count, 3);System.out.println(cnt);}@Testpublic void testUpdate() throws SQLException {int cnt = 0;String sql = SqlBuilder.build().update("tb_user", "password", "username").where("id", SqlCompareIdentity.EQ).sql();cnt = Operate.update(sql, "O", "t50", "13");System.out.println("影响了" + cnt + "条数据");String sql_no_tbname = SqlBuilder.build().tbName("tb_user").update(new String[]{"password", "username"}).where("id", SqlCompareIdentity.EQ).sql();cnt = Operate.update(sql_no_tbname, "Obu", "t50123", "13");System.out.println("影响了" + cnt + "条数据");}@Testpublic void testInsert() throws SQLException {int count = 0;String sql = SqlBuilder.build().insert("tb_user", "username", "password", "addr", "gender").sql();count = Operate.update(sql, "name", "pass", "cn", "男");System.out.println("影响了" + count + "条数据");}@Testpublic void testDelete() throws SQLException {int cnt = 0;String sql_tb = SqlBuilder.build().tbName("tb_user").delete().where("id", SqlCompareIdentity.EQ).sql();cnt = Operate.update(sql_tb, 219);System.out.println("影响了" + cnt + "条数据");}
这里的SqlCompareIdentity是为了方便管理,把一些比较关键字放到枚举里,然后在commons类里定义数组,根据enum的索引从数组里拿比较运算符来拼接
SqlCompareIdentity
public enum SqlCompareIdentity {GT,// >GE,// >=LT, // <LE,// <=EQ, // =NE, // !=
}
Common
public class Common {public static final String[] Compares = new String[]{">", ">=", "<", "<=", "=", "!=", "like", "in"};// baseCRUD方法的名称public static final String[] JOIN_TYPE = new String[]{"left","right","full","inner","left outer","right outer"};
}
SqlJoinType
public enum SqlJoinType {// "left","right","full","inner","left outer","right outer"LEFT,RIGHT,FULL,INNER,LO,RO
}
2. 创建BaseCRUDRepository,实现动态代理
思路:BaseCRUDRepository<T>里定义基本curd方法,select方法的返回值由传入的泛型决定,因为方法名是死的,所以可以通过方法名来判断是哪个方法,我们可以在动态代理里根据不同方法提供不同的实现,用户只需要创建接口继承BaseCRUDRepository,就可以使用这些方法
Repository是一个注解,主要是指定数据库表名用的
package org.malred.annotations;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {String value(); // 表名
}
Operate
这里的type1是执行jdbc方法需要的实体类类型,本来想通过泛型拿到,但是好像java不能反射获取类的泛型
// 定义在Operate类里public static <T> T getMapper(Class<?> mapperClass, Class<?> type1) {// 使用JDK动态代理为Dao接口生成代理对象,并返回Object proxyInstance = Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 拿到表名Repository annotation = mapperClass.getAnnotation(Repository.class);String tbName = annotation.value();// 拼装sql
// String sql = (String) Common.DefaultCRUDSql.get(method.getName());
// System.out.println(sql);String sql = "";// 默认CRUD接口的代理方法switch (method.getName()) {case "findAll": {sql = SqlBuilder.build().tbName(tbName).select().sql();System.out.println("执行findAll方法");System.out.println("当前执行的sql语句: " + sql);return Operate.getList(type1, sql);}case "findById": {sql = SqlBuilder.build().tbName(tbName).select().where("id", SqlCompareIdentity.EQ).sql();System.out.println("执行findById方法");System.out.println("当前执行的sql语句: " + sql);return Operate.get(type1, sql, args);}case "update": {ParseClazz parseClazz = parseObjectArgs(args);String[] paramNames = new String[parseClazz.params.keySet().size()];for (int i = 0; i < parseClazz.params.keySet().toArray().length; i++) {paramNames[i] = parseClazz.params.keySet().toArray()[i].toString();}sql = SqlBuilder.build().update(tbName, paramNames).where(parseClazz.idName, SqlCompareIdentity.EQ).sql();System.out.println("执行update方法");System.out.println("当前执行的sql语句: " + sql);String[] paramVals = new String[parseClazz.params.values().size() + 1];for (int i = 0; i < parseClazz.params.values().toArray().length; i++) {paramVals[i] = parseClazz.params.values().toArray()[i].toString();
// System.out.println(paramVals[i]);}paramVals[paramVals.length - 1] = parseClazz.idVal.toString();return Operate.update(sql, paramVals);}case "insert": {ParseClazz parseClazz = parseObjectArgs(args);String[] paramNames = new String[parseClazz.params.keySet().size()];for (int i = 0; i < parseClazz.params.keySet().toArray().length; i++) {paramNames[i] = parseClazz.params.keySet().toArray()[i].toString();}sql = SqlBuilder.build().tbName(tbName).insert(paramNames).sql();System.out.println("执行insert方法");System.out.println("当前执行的sql语句: " + sql);String[] paramVals = new String[parseClazz.params.values().size()];for (int i = 0; i < parseClazz.params.values().toArray().length; i++) {paramVals[i] = parseClazz.params.values().toArray()[i].toString();
// System.out.println(paramVals[i]);}return update(sql, paramVals);}case "delete": {sql = SqlBuilder.build().tbName(tbName).delete().where("id", SqlCompareIdentity.EQ).sql();System.out.println("执行delete方法");System.out.println("当前执行的sql语句: " + sql);return update(sql, args[0]);}}return null;}});return (T) proxyInstance;}
测试
// 基本CRUD接口的代理实现测试@Testpublic void testSelectProxy() {UserRepository mapper = Operate.getMapper(UserRepository.class, TbUser.class);
// UserRepository mapper = Operate.getMapper(UserRepository.class);List<TbUser> all = mapper.findAll();System.out.println(all);TbUser one = mapper.findById(1);System.out.println(one);}@Testpublic void testUpdateProxy() {int cnt = 0;
// UserRepository mapper = Operate.getMapper(UserRepository.class, TbUser.class);UserRepository mapper = Operate.getMapper(UserRepository.class);cnt = mapper.update(new TbUser(212, "ema1n", "s1sap", "女", null));System.out.println("影响了" + cnt + "条数据");// cnt = mapper.update(new TbUser(211, "name", "pass", null, null));
// System.out.println("影响了" + cnt + "条数据");}@Testpublic void testInsertProxy() {int cnt = 0;
// UserRepository mapper = Operate.getMapper(UserRepository.class, TbUser.class);UserRepository mapper = Operate.getMapper(UserRepository.class);cnt = mapper.insert(new TbUser(0, "eman", "ssap", null, null));System.out.println("影响了" + cnt + "条数据");}@Testpublic void testDeleteProxy() {int cnt = 0;UserRepository mapper = Operate.getMapper(UserRepository.class, TbUser.class);cnt = mapper.delete(223);System.out.println("影响了" + cnt + "条数据");}
3. 注解+接口方法
思路,依然是走动态代理,只是sql的获取不再是框架构建,而是用户在注解中定义,框架只是把值填充,这里我们需要注意,如果是第二种方法和注解的混用,会有一个类型问题
这个问题就是,如果传入了type1,那么所有被代理的方法如果都用type1作为调用jdbc时传入的实体类型,就无法使用其他类型(无法查询并封装到其他类型),而我们的解决方法是,注解的方法就根据注解方法的返回值来传递实体类型,base方法就用type1
public static <T> T getMapper(Class<?> mapperClass, Class<?> type1) {// 使用JDK动态代理为Dao接口生成代理对象,并返回Object proxyInstance = Proxy.newProxyInstance(Operate.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 用户定义的方法的返回类型Class<?> type = null;//获取方法的返回值类型Type genericReturnType = method.getGenericReturnType();if (!method.getName().equals("findAll") && !method.getName().equals("findById")) {if (genericReturnType instanceof ParameterizedType) {Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();for (Type parameterType : actualTypeArguments) {System.out.println(parameterType);type = (Class<?>) parameterType;}} else {type = (Class<?>) genericReturnType;}}// 拿到表名Repository annotation = mapperClass.getAnnotation(Repository.class);String tbName = annotation.value();// 拼装sql
// String sql = (String) Common.DefaultCRUDSql.get(method.getName());
// System.out.println(sql);String sql = "";// 默认CRUD接口的代理方法switch (method.getName()) {case "findAll": {sql = SqlBuilder.build().tbName(tbName).select().sql();System.out.println("执行findAll方法");System.out.println("当前执行的sql语句: " + sql);return Operate.getList(type1, sql);}case "findById": {sql = SqlBuilder.build().tbName(tbName).select().where("id", SqlCompareIdentity.EQ).sql();System.out.println("执行findById方法");System.out.println("当前执行的sql语句: " + sql);return Operate.get(type1, sql, args);}case "update": {ParseClazz parseClazz = parseObjectArgs(args);String[] paramNames = new String[parseClazz.params.keySet().size()];for (int i = 0; i < parseClazz.params.keySet().toArray().length; i++) {paramNames[i] = parseClazz.params.keySet().toArray()[i].toString();}sql = SqlBuilder.build().update(tbName, paramNames).where(parseClazz.idName, SqlCompareIdentity.EQ).sql();System.out.println("执行update方法");System.out.println("当前执行的sql语句: " + sql);String[] paramVals = new String[parseClazz.params.values().size() + 1];for (int i = 0; i < parseClazz.params.values().toArray().length; i++) {paramVals[i] = parseClazz.params.values().toArray()[i].toString();
// System.out.println(paramVals[i]);}paramVals[paramVals.length - 1] = parseClazz.idVal.toString();return Operate.update(sql, paramVals);}case "insert": {ParseClazz parseClazz = parseObjectArgs(args);String[] paramNames = new String[parseClazz.params.keySet().size()];for (int i = 0; i < parseClazz.params.keySet().toArray().length; i++) {paramNames[i] = parseClazz.params.keySet().toArray()[i].toString();}sql = SqlBuilder.build().tbName(tbName).insert(paramNames).sql();System.out.println("执行insert方法");System.out.println("当前执行的sql语句: " + sql);String[] paramVals = new String[parseClazz.params.values().size()];for (int i = 0; i < parseClazz.params.values().toArray().length; i++) {paramVals[i] = parseClazz.params.values().toArray()[i].toString();
// System.out.println(paramVals[i]);}return update(sql, paramVals);}case "delete": {sql = SqlBuilder.build().tbName(tbName).delete().where("id", SqlCompareIdentity.EQ).sql();System.out.println("执行delete方法");System.out.println("当前执行的sql语句: " + sql);return update(sql, args[0]);}}// 如果都不是上面的,就是用户自己定义的if (method.isAnnotationPresent(Select.class)) {Select selectAnno = method.getAnnotation(Select.class);sql = selectAnno.value();// 判断是查询单个还是多个(返回值类型是List之类的吗)// 这里只是简单判断一下
// Type genericReturnType = method.getGenericReturnType();// 判断是否进行了泛型类型参数化(是否有泛型)if (genericReturnType instanceof ParameterizedType) {
// if (x instanceof Collection< ? >){
// }
// if (x instanceof Map<?,?>){
// }return Operate.getList(type, sql, args);}return Operate.get(type, sql, args);}if (method.isAnnotationPresent(Update.class)) {Update anno = method.getAnnotation(Update.class);sql = anno.value();return update(sql, args);}if (method.isAnnotationPresent(Delete.class)) {Delete anno = method.getAnnotation(Delete.class);sql = anno.value();return update(sql, args);}if (method.isAnnotationPresent(Insert.class)) {Insert anno = method.getAnnotation(Insert.class);sql = anno.value();return update(sql, args);}// 返回值return null;}});return (T) proxyInstance;}
测试
// 用户定义的注解的代理实现测试@Testpublic void testSelectAnnotation() {UserRepository mapper = Operate.getMapper(UserRepository.class);
// UserRepository mapper = Operate.getMapper(UserRepository.class, TbUser.class);TbUser user = mapper.selectOneByUsername("张三");System.out.println(user);List<TbUser> tbUsers = mapper.selectOneByNEPassword("456");for (TbUser tbUser : tbUsers) {System.out.println(tbUser);}// 和因为在代理时写死传入的返回值类型,这里只能重新创建代理// 根据接口方法的返回值获取泛型类型,动态判断返回类型
// UserRepository puMapper =
// Operate.getMapper(UserRepository.class, ProductAndUser.class);
// UserRepository puMapper = Operate.getMapper(UserRepository.class);List<ProductAndUser> userAndProductJoin = mapper.findUserAndProductJoin(7);System.out.println(userAndProductJoin);}@Testpublic void testUpdateAnnotation() {
// UserRepository mapper =
// Operate.getMapper(UserRepository.class, TbUser.class);UserRepository mapper = Operate.getMapper(UserRepository.class);int cnt = mapper.uptUser("哇哈哈1", 14);System.out.println("影响了" + cnt + "条数据");}@Testpublic void testDeleteAnnotation() {
// UserRepository mapper = Operate.getMapper(UserRepository.class, TbUser.class);UserRepository mapper = Operate.getMapper(UserRepository.class);int cnt = mapper.delUser(218, "tpass");System.out.println("影响了" + cnt + "条数据");}@Testpublic void testInsertAnnotation() {
// UserRepository mapper = Operate.getMapper(UserRepository.class, TbUser.class);UserRepository mapper = Operate.getMapper(UserRepository.class);int cnt = mapper.addUser("tttt", "tpass", "tttt2", "tpass2");System.out.println("影响了" + cnt + "条数据");}
下一步: 接口代理的selectBy实体类属性名方法
思路:实体类加上注解,指定表名,Operator的static里扫描注解并将key-表名:val-扫到的类注入到一个map,代理方法里通过Repository里的表名和map里的匹配,拿到实体类,拼接字符串selectByxxx,放入list,当执行方法,进入代理,就判断是否在list中包含,然后实现代理逻辑
相关文章:
封装JDBC,实现简单ORM框架
本文将封装JDBC的操作,实现简单的ORM框架,提供3种风格的api来给用户使用(1.原生jdbcSqlBuilder;2.类似jpa和mp的;3.注解接口方法) 代码仓库:malred/IFullORM 1. 原生JDBCsql构建器 第一步&…...
监控与运维,主流it运维监控工具
IT监管和运行维护已成为企业经营的关键环节。本文将详细介绍IT监管和运行维护的必要性、主要功能和实施策略,帮助企业实现数据安全和高效运行。 IT监管和运行维护的必要性 确保企业数据安全 IT监控系统可以实时监控企业网络、服务器、存储等关键设备的运行情况&…...
基于Matlab实现全局优化算法
Matlab是一种非常强大的数学建模和计算工具,它提供了许多优化算法的实现。全局优化算法是一种能够找到全局最优解的优化算法,相对于局部优化算法来说,具有更强的全局搜索能力。在本文中,我们将介绍如何使用Matlab实现全局优化算法…...
Kafka 笔记 (Non-Root/Container)
目录 1. Kafka 笔记 (Non-Root/Container)1.1. 启动1.2. bitnami/kafka1.2.1. Non-Root Containers 1. Kafka 笔记 (Non-Root/Container) 1.1. 启动 Kafka 需要与 ZooKeeper 一起启动: Kafka with ZooKeeper Run the following commands in order to start all services in…...
【Pytest】跳过执行之@pytest.mark.skip()详解
一、skip介绍及运用 在我们自动化测试过程中,经常会遇到功能阻塞、功能未实现、环境等一系列外部因素问题导致的一些用例执行不了,这时我们就可以用到跳过skip用例,如果我们注释掉或删除掉,后面还要进行恢复操作。 1、skip跳过成…...
Android Framework 常见解决方案(22)防应用被LowMemoryKillerDaemon(LMKD)杀掉
1 原理说明 LMKD 借助 Linux 内核的 OOM(Out of Memory)机制来管理内存。当系统内存不足时,OOM 触发器会发送信号给LMKD,通知其进行内存管理。LMKD根据预先定义的策略和优先级,选择性地终止一些进程,以释放…...
Vue - 组件递归
目录 组件递归子组件父组件 组件递归 当要渲染一个目录时,因为可能有嵌套数据,并且组件的层级未知,可以使用组件递归来解决 注意点: 1,使用递归时必须提供 name,也就是通过组件的 name 递归自己。 2&am…...
微信小程序案例2-1:学生信息
文章目录 (二)准备图像素材(三)编写小程序页面结构 单击[确认] 清空页面结构文件index.wxml内容 修改页面配置文件index.json,不适用navigation-bar组件 删除全局配置文件app.json,删除渲染器配置&a…...
小程序如何设置余额充值
在小程序中设置余额充值是一种非常有效的方式,可以帮助商家吸引更多的会员并提高用户的消费频率。下面将介绍如何在小程序中设置余额充值并使用。 第一步:创建充值方案 在小程序管理员后台->营销管理->余额充值页面,添加充值方案。可…...
vue项目打包成H5apk中使用语音播放
利用浏览器语音播放api功能,在vue项目中调用api实现语音播报。 在mounted生命周期函数中获取浏览器的SpeechSynthesis API data() {return {speech: null,};},mounted() {if ("SpeechSynthesisUtterance" in window) {this.speech window.speechSynthesi…...
windows:批处理bat实例
文章目录 文件/文件夹管理实例批量更改文件名创建编号从0到9的10个文件自动循环运行某个程序显示批处理的完整路径信息将文件名更名为当前系统日期使用批处理命令自动接收用户输入的信息计算当前目录及子目录(中文件)所占硬盘空间自动删除当前目录及子目…...
websocket php教程
WebSocket 是 HTML5 提供的一种网络通讯协议,用于服务端与客户端实时数据传输。广泛用于浏览器与服务器的实时通讯,APP与服务器的实时通讯等场景。 相比传统HTTP协议请求响应式通讯,WebSocket协议可以做到实时的双向通讯,服务端可…...
【操作系统】调度算法的评价指标和三种调度算法
🐌个人主页: 🐌 叶落闲庭 💨我的专栏:💨 c语言 数据结构 javaEE 操作系统 Redis 石可破也,而不可夺坚;丹可磨也,而不可夺赤。 操作系统 一、调度算法的评价指标1.1 CPU利…...
CSS详细基础(三)复合选择器
前两章介绍了CSS中的基础属性,以及一些基础的选择器,本贴开始介绍复合选择器的内容~ 在 CSS 中,可以根据选择器的类型把选择器分为基础选择器和复合选择器,复合选择器是建立在基础选择器之上,对基本选择器进行组合形…...
LeetCode【2251. 花期内花的数目】
给你一个下标从 0 开始的二维整数数组 flowers ,其中 flowers[i] [starti, endi] 表示第 i 朵花的 花期 从 starti 到 endi (都 包含)。同时给你一个下标从 0 开始大小为 n 的整数数组 people ,people[i] 是第 i 个人来看花的时间…...
大数据(九):数据可视化(一)
专栏介绍 结合自身经验和内部资料总结的Python教程,每天3-5章,最短1个月就能全方位的完成Python的学习并进行实战开发,学完了定能成为大佬!加油吧!卷起来! 全部文章请访问专栏:《Python全栈教程(0基础)》 再推荐一下最近热更的:《大厂测试高频面试题详解》 该专栏对…...
讲讲项目里的仪表盘编辑器(二)
应用场景 正常来说,编辑器应用场景应该包括: 编辑器-预览 编辑器 最终运行时 怎么去设计 上一篇推文,我们已经大概了解了编辑器场景。接下来,我们来看预览时的设计 编辑器-预览 点击预览按钮,执行以…...
文心一言 VS 讯飞星火 VS chatgpt (102)-- 算法导论9.3 8题
八、用go语言,设 X[1…n]和 Y[1…n]为两个数组,每个都包含n个有序的元素。请设计一个 O(lgn)时间的算法来找出数组 X和Y中所有 2n 个元素的中位数。 文心一言: 要在 O(lgn) 时间内找出两个有序数组 X 和 Y 中所有元素的中位数,可…...
axios添加缓存请求,防止多次请求,单页面多个同一组件造成多次请求解决方案
在axios中,添加 const cacheMap {};响应拦截添加 try {// 删除缓存const api res.config.url.replace(process.env.VUE_APP_BASE_API, "");if (cacheMap.hasOwnProperty(api)) {delete cacheMap[api];}} catch (err) {}创建两个请求方法 /*** Get缓存…...
Java包装类与自动拆箱装箱
有的时候博客内容会有变动,首发博客是最新的,其他博客地址可能会未同步,认准https://blog.zysicyj.top 首发博客地址[1] 面试题手册[2] 系列文章地址[3] 1. 什么是 Java 包装类和自动拆箱装箱? Java 中的基本数据类型(如 int、cha…...
基于SpringBoot网上超市的设计与实现【附万字文档(LW)和搭建文档】
主要功能 前台登录: 注册用户:用户名、密码、姓名、联系电话 用户: ①首页、商品信息推荐、商品资讯、查看更多 ②商品信息、商品详情、评论、点我收藏、添加购物车、立即购买 ③个人中心、余额、点我充值、更新信息、我的订单、我的地址、我…...
二、C++项目:仿muduo库实现并发服务器之时间轮的设计
文章目录 一、为什么要设计时间轮?(一)简单的秒级定时任务实现:(二)Linux提供给我们的定时器:1.原型2.例子 二、时间轮(一)思想(一)代码 一、为什…...
计算机竞赛 深度学习OCR中文识别 - opencv python
文章目录 0 前言1 课题背景2 实现效果3 文本区域检测网络-CTPN4 文本识别网络-CRNN5 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 **基于深度学习OCR中文识别系统 ** 该项目较为新颖,适合作为竞赛课题方向,…...
蓝桥等考Python组别五级003
第一部分:选择题 1、Python L5 (15分) 表达式“a >= b”等价于下面哪个表达式?( ) a > b and a == ba > b or a == ba < b and a == ba < b or a > b正确答案:B 2、Python L5 (15分) 当x是偶数时,下面哪个表达式的值一定是True?( …...
学之思项目第一天-完成项目搭建
一、前端 拉下前端代码执行 npm i 然后执行npm run serve就行了 二、后端 搭建父子模块 因为这个涉及到前台以及后台管理所以使用父子模块 并且放置一个公共模块,放置公共的依赖以及公共的代码 2.1 搭建父子工程 这个可以使用直接一个个的maven模块ÿ…...
pandas--->CSV / JSON
csv CSV(Comma-Separated Values,逗号分隔值,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。 CSV 是一种通用的、相对简单的文…...
LeetCode算法二叉树—116. 填充每个节点的下一个右侧节点指针
目录 116. 填充每个节点的下一个右侧节点指针 题解: 代码: 运行结果: 给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: struct Node {int val;Node *left;N…...
二、2023.9.28.C++基础endC++内存end.2
文章目录 17、说说new和malloc的区别,各自底层实现原理。18、 说说const和define的区别。19、 说说C中函数指针和指针函数的区别?20、 说说const int *a, int const *a, const int a, int *const a, const int *const a分别是什么,有什么特点…...
DevSecOps 将会嵌入 DevOps
通常人们在一个项目行将结束时才会考虑到安全,这么做会导致很多问题;将安全融入到DevOps的工作流中已产生了积极结果。 DevSecOps:安全正当时 一直以来,开发人员在构建软件时认为功能需求优先于安全。虽然安全编码实践起着重要作…...
不同管径地下管线的地质雷达响应特征分析
不同管径地下管线的地质雷达响应特征分析 前言 以混凝土管线为例,建立了不同管径的城市地下管线模型,进行二维地质雷达正演模拟,分析不同管径管线的地质雷达响应特征。 文章目录 不同管径地下管线的地质雷达响应特征分析前言1、管径50cm2、…...
婚纱摄影网站设计案例/站长工具seo综合查询访问
启动服务器:zkServer.sh start启动客户端:zkCli.sh -server 127.0.0.1:2181ls / 显示create /zk_test my_data 新建; create -e /zk_test my_data 是创建临时节点 -s 创建顺序节点,利用这个特性可以实现分布式锁:crea…...
网站推广 济南/湘潭网站设计外包服务
GUI程序的基本结构 基本结构如下: # 导入需要的包 from PyQt5.Qt import * import sysapp QApplication(sys.argv) #创建一个应用程序(比不可少的)代码主功能模块区 #控件操作#窗口显示 #开始执行应用程序,并进入消息循环 sys…...
泰兴做网站公司/免费拓客软件哪个好用
问题 使用Tomcat时候控制台总是打印一些中文乱码,虽然不影响使用但是看着就难受。 包括:Server、Tomcat Localhost Log、Tomcat Catalina Log 原因分析 Windows环境下,中文编码为GBK 解决方案 修改文件,文件路径:%…...
能发外链的网站/seo线上培训班
没有使用igraph库哦 因为我还没学小世界网络简介:1998年, Watts和Strogatz 提出了小世界网络这一概念,并建立了WS模型。实证结果表明,大多数的真实网络都具有小世界特性(较小的最短路径)和聚类特性(较大的聚类系数)。传统的规则最近邻耦合网络…...
邯郸旅游景点/太原seo计费管理
http://www.cnblogs.com/qrlozte/p/3532522.html ************************** 我以前一直不知道怎么在eclipse中调试web项目,比如说我在某个Servlet或者Action中打了断点,然后该怎么调试呢 今天偶然发现了原来是可以的,方法如下: …...
好网站建设公司服务/百度代运营公司
内外网同时上网 不少公司的网管试图解决双网卡问题,下面我就给大家详细的讲解一下双网卡同时使用的方法,这样即可保障内网的安全,又能解决电脑访问外网的问题,一举两得。希望大家喜欢。 首先你的机器需要有两块网卡,…...