学习内容》:
//========Java学习内容=========
1.直播课—-JDBC封装第五天
《代码内容》:
/** 代码思路 :
因为之前的封装在dao层需要两条代码就可以完成数据库的CURD ,但是现在我们想把他封装的更加完美,利用反射注解,动态代理的方式
通过注解传SQL语句 让底层去读取注解的sql和他的类型去判断做什么操作,让代理去执行操作,这样dao层就成为了一个接口。
实现思路:
在SqlSession下设计一个方法 用来反射读取注解中的sql用动态代理的方式去执行对应的方法。
**/
//===================================================================================//
SqlSession类 ——>
添加了一个getMapper方法 用来读取注解的SQL通过动态代理的方式执行对应的CURD的方法
package orm;import com.mysql.cj.jdbc.ParameterBindingsImpl;import com.sun.source.util.ParameterNameProvider;import domain.Atm;import domain.KeyAndSql;import orm.annotation.Delete;import orm.annotation.Insert;import orm.annotation.Select;import orm.annotation.Update;import java.lang.annotation.Annotation;import java.lang.reflect.*;import java.sql.Connection;import java.sql.DriverManager;import java.sql.PreparedStatement;import java.sql.ResultSet;import java.util.ArrayList;import java.util.List;import java.util.Map;@SuppressWarnings("all")public class SqlSession {/***因为下面update方法的返回值是一个Atm对象 如果这里返回一个集合的话 整体用户体验不太好* 思考:我们需要把这个Query(查询) 让他的返回值也变成一个对象* 策略设计模式--->匿名内部类实现策略**//*** 由于新增一条记录 删除一条记录 修改一条记录 代码中除了sql语句和预处理?的值不同,其他的代码都一样* 如果进行优化的话 参数问题??--->因为只有sql不同,那么参数需要传一条sql 预处理用动态参数列表* 若这样封装可以省2次的代码* 返回值-->可有可无 可以是修改的行数 或者 无* *//**来一个Handler小弟 给我解析 拼接**/private Handler handler = new Handler();/**写一些连接数据库时需要用到的参数**///Driver类加载地址private String driver="com.mysql.cj.jdbc.Driver";//连接需要的参数url user passwordprivate String url = "jdbc:mysql://localhost:3306/atm?serverTimezone=CST&characterEncoding=utf-8";private String user = "root";private String password = "at123123";/**查询单表格一条记录**/ /**但是需要三个参数,一条SQL一个策略-->策略设计模式,按顺序给我sql中预处理?的值**//**方法重载 另一个是给sql,预处理参数,存储的对象类型**/public <T>T queryOne(String sql, RowMapper rm, Object... objs){return (T)this.queryList(sql,rm,objs).get(0);}public <T>T queryOne(String sql,Object obj,Class resultType){return (T)this.queryList(sql,obj,resultType).get(0);}/**查询单表格多条记录**//**设计一个方法 之前的QueryList方法如果查询的话在Dao层用户需要去实现一个策略,这样体验感不太好** 我现在需要把他设计成 你只需要给我一个sql,给我sql中预处理的参数,再给我一个Class类型的类* 我就帮你把查询到的数据,存储在你给我的这个类中去* 参数问题??---->Sql肯定需要给我,预处理sql的?值你需要给我,还肯定需要各位Class的类型* *//**======查询单表格的多条记录======**/public <T> List<T> queryList(String sql, RowMapper rm, Object... objs){ArrayList<T> lists = new ArrayList<>();try {Class.forName(driver);Connection conn = DriverManager.getConnection(url, user, password);PreparedStatement pstat = conn.prepareStatement(sql);for (int i = 0; i <objs.length ; i++) {pstat.setObject(i+1,objs[i]);}ResultSet rst = pstat.executeQuery();while(rst.next()){lists.add(rm.mapperRow(rst));}rst.close();pstat.close();conn.close();} catch (Exception e) {e.printStackTrace();}return lists;}public <T> List<T> queryList(String sql,Object obj,Class resultType){ArrayList<T> lists = new ArrayList<>();try {/**JDBC基本操作**/Class.forName(driver);Connection conn = DriverManager.getConnection(url, user, password);/**在给状态参数之前要把给的属性名提前的拿到,并存集合中字段名 然后再给他一条他认识的Sql**/KeyAndSql keyAndSql = handler.parseSql(sql);PreparedStatement pstat = conn.prepareStatement(keyAndSql.getNewSql());/**给赋值**/if (obj != null){handler.handlerParam(pstat,obj,keyAndSql.getColumnName());}ResultSet rst = pstat.executeQuery();while (rst.next()){/**调用handler中的方法来往class里面赋值**/lists.add((T)handler.handlerResult(rst,resultType));}rst.close();pstat.close();conn.close();} catch (Exception e) {e.printStackTrace();}return lists;}/**JDBC第三天的框架封装,之前的Update方法参数传的是一个对象* 但是现在是Object... 这个东西看起来不太好--->而且需要你给我顺序才可以* 我需要实现---->你给我一条按照我的格式来写的sql语句 和一个 =对象=,更新任意一条记录* ==========================================================================* 思考:--->是否需要返回值---->修改的行数 或 不需要* ---->参数需要怎么给 ----> Sql肯定得给,然后我们不需要数组, 直接给我一个对象* ==========================================================================* 思路--->* //1.按照 #{}这种格式给我一条sql* //2.我把这条sql给拆开,然后把这里面给的表的字段的顺序和字段名 存在--->集合中* //3.并且把他拼接回认识的?组成一条之前那种JDBC认识的SQL语句* //4.将拿到的字段名通过反射domain实体类或Map或基本类型,各种类型的判断,不同分支的赋值到sql中* **//**例如SQL---- insert into user values(#{uname},#{upassword},#{ubal}) **//**新增,删除,更新 各种表格一条记录**//**构成方法重载,一个动态参数列表,一个是给对象**/public void update(String sql,Object... objs){try {Class.forName(driver);Connection conn = DriverManager.getConnection(url, user, password);PreparedStatement pstat = conn.prepareStatement(sql);for (int i = 0; i <objs.length ; i++) {pstat.setObject(i+1,objs[i]);}pstat.executeUpdate();pstat.close();conn.close();} catch (Exception e) {e.printStackTrace();}}public void insert(String sql,Object... objs){this.update(sql,objs);}public void delete(String sql,Object... objs){this.update(sql,objs);}public void update(String sql,Object obj){try {Class.forName(driver);Connection conn = DriverManager.getConnection(url, user, password);//在这里 提前把这条sql给解析好 存在 集合中字段名和顺序//并且 把用完的sql语句 再拼接回可以解析的带?的sqlKeyAndSql keyAndSql = handler.parseSql(sql);PreparedStatement pstat = conn.prepareStatement(keyAndSql.getNewSql());//如果你给我的Obj对象不是空的if (obj != null){//将sql中的?分别赋值 在执行类Handler里面再写一个 把参数赋值的方法handler.handlerParam(pstat,obj,keyAndSql.getColumnName());}pstat.executeUpdate();pstat.close();conn.close();} catch (Exception e) {e.printStackTrace();}}public void insert(String sql,Object obj){this.update(sql,obj);}public void delete(String sql,Object obj){this.update(sql,obj);}/*** JDBC封装第五天 如果我想通过注解的方式去读取注解里面的Sql然后自动取执行对应的方法* 利用 --->反射实现设计一个getMapper方法* 动态代理---->* **/public <T> T getMapper(Class clazz){//第一个参数是类加载器//第二个参数是被代理对象(dao类)//第三个参数是这个代理的具体实现--->这里需要自己写策略实现return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//proxy就是这个代理对象//method被代理的那个方法(要方法上面注解里的Sql)//args被代理的那个方法的参数(存储的是Sql上的那些所需的值)//获取方法上的sql/*** SqlSession需要的参数--One-->sql,对象* **/Annotation an = method.getAnnotations()[0];//获取当前注解的类型(Insrt.class还是select.class)Class anType = an.annotationType();//获取当前注解内的SqlMethod anValue = anType.getDeclaredMethod("value");String sql = (String)anValue.invoke(an);//处理被代理对象中方法中的参数Object param = args == null ? null : args[0];//判断注解类型去分别执行SqlSessionif (anType == Insert.class){SqlSession.this.insert(sql,param);}else if (anType == Update.class){SqlSession.this.update(sql,param);}else if (anType == Delete.class){SqlSession.this.delete(sql,param);}else if (anType == Select.class){Class<?> methodReturnType = method.getReturnType();if (methodReturnType != List.class){return SqlSession.this.queryOne(sql,param,methodReturnType);}//获取方法的返回值具体类型System.out.println("执行了");Type listReturnType = method.getGenericReturnType();//向下多态转型if (listReturnType instanceof ParameterizedType) {ParameterizedType realReturnType = (ParameterizedType) listReturnType;//操作返回值类型中的泛型Class domain = (Class) realReturnType.getActualTypeArguments()[0];System.out.println("执行了查询多条");return SqlSession.this.queryList(sql, param, domain);}}return null;}});}}
学习总结:
学懂的:
思路可以跟的上,自己通过慢慢的想思路去实现了这个功能!
有问题的地方:
args[0]不晓得为啥要传这个参数
在反射泛型向下转型ParameterizedType 这个类型的时候出现报错cast….
经过自己的摸索发现JDK版本可能不同,在JDK13版本可能需要在获取返回值内的泛型的时候,如果要向下转型获取到我们可以操作
的那种类型,我们就必须判断一下获取到的返回值内的泛型是不是一个ParameterizedType类型,才可以继续进行反射操作
