学习内容》:

    //========Java学习内容=========
    1.直播课—-JDBC封装第五天

    《代码内容》:
    /** 代码思路 :
    因为之前的封装在dao层需要两条代码就可以完成数据库的CURD ,但是现在我们想把他封装的更加完美,利用反射注解,动态代理的方式
    通过注解传SQL语句 让底层去读取注解的sql和他的类型去判断做什么操作,让代理去执行操作,这样dao层就成为了一个接口。

    实现思路:
    在SqlSession下设计一个方法 用来反射读取注解中的sql用动态代理的方式去执行对应的方法。
    **/
    //===================================================================================//

    SqlSession类 ——>
    添加了一个getMapper方法 用来读取注解的SQL通过动态代理的方式执行对应的CURD的方法

    1. package orm;
    2. import com.mysql.cj.jdbc.ParameterBindingsImpl;
    3. import com.sun.source.util.ParameterNameProvider;
    4. import domain.Atm;
    5. import domain.KeyAndSql;
    6. import orm.annotation.Delete;
    7. import orm.annotation.Insert;
    8. import orm.annotation.Select;
    9. import orm.annotation.Update;
    10. import java.lang.annotation.Annotation;
    11. import java.lang.reflect.*;
    12. import java.sql.Connection;
    13. import java.sql.DriverManager;
    14. import java.sql.PreparedStatement;
    15. import java.sql.ResultSet;
    16. import java.util.ArrayList;
    17. import java.util.List;
    18. import java.util.Map;
    19. @SuppressWarnings("all")
    20. public class SqlSession {
    21. /**
    22. *因为下面update方法的返回值是一个Atm对象 如果这里返回一个集合的话 整体用户体验不太好
    23. * 思考:我们需要把这个Query(查询) 让他的返回值也变成一个对象
    24. * 策略设计模式--->匿名内部类实现策略
    25. **/
    26. /**
    27. * 由于新增一条记录 删除一条记录 修改一条记录 代码中除了sql语句和预处理?的值不同,其他的代码都一样
    28. * 如果进行优化的话 参数问题??--->因为只有sql不同,那么参数需要传一条sql 预处理用动态参数列表
    29. * 若这样封装可以省2次的代码
    30. * 返回值-->可有可无 可以是修改的行数 或者 无
    31. * */
    32. /**来一个Handler小弟 给我解析 拼接**/
    33. private Handler handler = new Handler();
    34. /**写一些连接数据库时需要用到的参数**/
    35. //Driver类加载地址
    36. private String driver="com.mysql.cj.jdbc.Driver";
    37. //连接需要的参数url user password
    38. private String url = "jdbc:mysql://localhost:3306/atm?serverTimezone=CST&characterEncoding=utf-8";
    39. private String user = "root";
    40. private String password = "at123123";
    41. /**查询单表格一条记录**/ /**但是需要三个参数,一条SQL一个策略-->策略设计模式,按顺序给我sql中预处理?的值**/
    42. /**方法重载 另一个是给sql,预处理参数,存储的对象类型**/
    43. public <T>T queryOne(String sql, RowMapper rm, Object... objs){
    44. return (T)this.queryList(sql,rm,objs).get(0);
    45. }
    46. public <T>T queryOne(String sql,Object obj,Class resultType){return (T)this.queryList(sql,obj,resultType).get(0);}
    47. /**查询单表格多条记录**/
    48. /**设计一个方法 之前的QueryList方法如果查询的话在Dao层用户需要去实现一个策略,这样体验感不太好*
    49. * 我现在需要把他设计成 你只需要给我一个sql,给我sql中预处理的参数,再给我一个Class类型的类
    50. * 我就帮你把查询到的数据,存储在你给我的这个类中去
    51. * 参数问题??---->Sql肯定需要给我,预处理sql的?值你需要给我,还肯定需要各位Class的类型
    52. * */
    53. /**======查询单表格的多条记录======**/
    54. public <T> List<T> queryList(String sql, RowMapper rm, Object... objs){
    55. ArrayList<T> lists = new ArrayList<>();
    56. try {
    57. Class.forName(driver);
    58. Connection conn = DriverManager.getConnection(url, user, password);
    59. PreparedStatement pstat = conn.prepareStatement(sql);
    60. for (int i = 0; i <objs.length ; i++) {
    61. pstat.setObject(i+1,objs[i]);
    62. }
    63. ResultSet rst = pstat.executeQuery();
    64. while(rst.next()){
    65. lists.add(rm.mapperRow(rst));
    66. }
    67. rst.close();
    68. pstat.close();
    69. conn.close();
    70. } catch (Exception e) {
    71. e.printStackTrace();
    72. }
    73. return lists;
    74. }
    75. public <T> List<T> queryList(String sql,Object obj,Class resultType){
    76. ArrayList<T> lists = new ArrayList<>();
    77. try {
    78. /**JDBC基本操作**/
    79. Class.forName(driver);
    80. Connection conn = DriverManager.getConnection(url, user, password);
    81. /**在给状态参数之前要把给的属性名提前的拿到,并存集合中字段名 然后再给他一条他认识的Sql**/
    82. KeyAndSql keyAndSql = handler.parseSql(sql);
    83. PreparedStatement pstat = conn.prepareStatement(keyAndSql.getNewSql());
    84. /**给赋值**/
    85. if (obj != null){handler.handlerParam(pstat,obj,keyAndSql.getColumnName());}
    86. ResultSet rst = pstat.executeQuery();
    87. while (rst.next()){
    88. /**调用handler中的方法来往class里面赋值**/
    89. lists.add((T)handler.handlerResult(rst,resultType));
    90. }
    91. rst.close();
    92. pstat.close();
    93. conn.close();
    94. } catch (Exception e) {
    95. e.printStackTrace();
    96. }
    97. return lists;
    98. }
    99. /**JDBC第三天的框架封装,之前的Update方法参数传的是一个对象
    100. * 但是现在是Object... 这个东西看起来不太好--->而且需要你给我顺序才可以
    101. * 我需要实现---->你给我一条按照我的格式来写的sql语句 和一个 =对象=,更新任意一条记录
    102. * ==========================================================================
    103. * 思考:--->是否需要返回值---->修改的行数 或 不需要
    104. * ---->参数需要怎么给 ----> Sql肯定得给,然后我们不需要数组, 直接给我一个对象
    105. * ==========================================================================
    106. * 思路--->
    107. * //1.按照 #{}这种格式给我一条sql
    108. * //2.我把这条sql给拆开,然后把这里面给的表的字段的顺序和字段名 存在--->集合中
    109. * //3.并且把他拼接回认识的?组成一条之前那种JDBC认识的SQL语句
    110. * //4.将拿到的字段名通过反射domain实体类或Map或基本类型,各种类型的判断,不同分支的赋值到sql中
    111. * **/
    112. /**例如SQL---- insert into user values(#{uname},#{upassword},#{ubal}) **/
    113. /**新增,删除,更新 各种表格一条记录**/
    114. /**构成方法重载,一个动态参数列表,一个是给对象**/
    115. public void update(String sql,Object... objs){
    116. try {
    117. Class.forName(driver);
    118. Connection conn = DriverManager.getConnection(url, user, password);
    119. PreparedStatement pstat = conn.prepareStatement(sql);
    120. for (int i = 0; i <objs.length ; i++) {
    121. pstat.setObject(i+1,objs[i]);
    122. }
    123. pstat.executeUpdate();
    124. pstat.close();
    125. conn.close();
    126. } catch (Exception e) {
    127. e.printStackTrace();
    128. }
    129. }
    130. public void insert(String sql,Object... objs){this.update(sql,objs);}
    131. public void delete(String sql,Object... objs){this.update(sql,objs);}
    132. public void update(String sql,Object obj){
    133. try {
    134. Class.forName(driver);
    135. Connection conn = DriverManager.getConnection(url, user, password);
    136. //在这里 提前把这条sql给解析好 存在 集合中字段名和顺序
    137. //并且 把用完的sql语句 再拼接回可以解析的带?的sql
    138. KeyAndSql keyAndSql = handler.parseSql(sql);
    139. PreparedStatement pstat = conn.prepareStatement(keyAndSql.getNewSql());
    140. //如果你给我的Obj对象不是空的
    141. if (obj != null){
    142. //将sql中的?分别赋值 在执行类Handler里面再写一个 把参数赋值的方法
    143. handler.handlerParam(pstat,obj,keyAndSql.getColumnName());
    144. }
    145. pstat.executeUpdate();
    146. pstat.close();
    147. conn.close();
    148. } catch (Exception e) {
    149. e.printStackTrace();
    150. }
    151. }
    152. public void insert(String sql,Object obj){this.update(sql,obj);}
    153. public void delete(String sql,Object obj){this.update(sql,obj);}
    154. /**
    155. * JDBC封装第五天 如果我想通过注解的方式去读取注解里面的Sql然后自动取执行对应的方法
    156. * 利用 --->反射实现设计一个getMapper方法
    157. * 动态代理---->
    158. * **/
    159. public <T> T getMapper(Class clazz){
    160. //第一个参数是类加载器
    161. //第二个参数是被代理对象(dao类)
    162. //第三个参数是这个代理的具体实现--->这里需要自己写策略实现
    163. return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
    164. @Override
    165. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    166. //proxy就是这个代理对象
    167. //method被代理的那个方法(要方法上面注解里的Sql)
    168. //args被代理的那个方法的参数(存储的是Sql上的那些所需的值)
    169. //获取方法上的sql
    170. /**
    171. * SqlSession需要的参数--One-->sql,对象
    172. * **/
    173. Annotation an = method.getAnnotations()[0];
    174. //获取当前注解的类型(Insrt.class还是select.class)
    175. Class anType = an.annotationType();
    176. //获取当前注解内的Sql
    177. Method anValue = anType.getDeclaredMethod("value");
    178. String sql = (String)anValue.invoke(an);
    179. //处理被代理对象中方法中的参数
    180. Object param = args == null ? null : args[0];
    181. //判断注解类型去分别执行SqlSession
    182. if (anType == Insert.class){
    183. SqlSession.this.insert(sql,param);
    184. }else if (anType == Update.class){
    185. SqlSession.this.update(sql,param);
    186. }else if (anType == Delete.class){
    187. SqlSession.this.delete(sql,param);
    188. }else if (anType == Select.class){
    189. Class<?> methodReturnType = method.getReturnType();
    190. if (methodReturnType != List.class){
    191. return SqlSession.this.queryOne(sql,param,methodReturnType);
    192. }
    193. //获取方法的返回值具体类型
    194. System.out.println("执行了");
    195. Type listReturnType = method.getGenericReturnType();
    196. //向下多态转型
    197. if (listReturnType instanceof ParameterizedType) {
    198. ParameterizedType realReturnType = (ParameterizedType) listReturnType;
    199. //操作返回值类型中的泛型
    200. Class domain = (Class) realReturnType.getActualTypeArguments()[0];
    201. System.out.println("执行了查询多条");
    202. return SqlSession.this.queryList(sql, param, domain);
    203. }
    204. }
    205. return null;
    206. }
    207. });
    208. }
    209. }

    学习总结:
    学懂的:
    思路可以跟的上,自己通过慢慢的想思路去实现了这个功能!
    有问题的地方:
    args[0]不晓得为啥要传这个参数
    在反射泛型向下转型ParameterizedType 这个类型的时候出现报错cast….
    image.png
    经过自己的摸索发现JDK版本可能不同,在JDK13版本可能需要在获取返回值内的泛型的时候,如果要向下转型获取到我们可以操作
    的那种类型,我们就必须判断一下获取到的返回值内的泛型是不是一个ParameterizedType类型,才可以继续进行反射操作