学习内容》:
//========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 password
private 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语句 再拼接回可以解析的带?的sql
KeyAndSql 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() {
@Override
public 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();
//获取当前注解内的Sql
Method anValue = anType.getDeclaredMethod("value");
String sql = (String)anValue.invoke(an);
//处理被代理对象中方法中的参数
Object param = args == null ? null : args[0];
//判断注解类型去分别执行SqlSession
if (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类型,才可以继续进行反射操作