JDBC中 DAO<T>的设计和优化遇见问题我的解决方法

1 篇文章 0 订阅
1 篇文章 0 订阅

一段头疼的代码

代码段

public class DAO<T>{
    private Class<T> clazz = null;
    {
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) type;
        Type[] types = parameterizedType.getActualTypeArguments();
        this.clazz = (Class<T>) types[0];
    }
    // .... 
}

  // 1. 获取子类的原型对象
        Class<? extends BaseDAO> childClass = this.getClass();
        // 2. 获取当前子类的直接父类的类型
        Type type = childClass.getGenericSuperclass();
        // 3.获取父类泛型的参数化类型
        ParameterizedType parameterizedType = (ParameterizedType) type;
        // 4. 获取所有参数对象
        Type[] types = parameterizedType.getActualTypeArguments();
        // 5.获得第一个参数对象 并赋值给 cls
        this.cls = (Class<T>)types[0];

今天在学习 DAO 的优化的时候,这一段代码让我很头痛,经过我的一番找博客,敲代码验证,功夫不负有心人,终于被我弄懂了

写这段代码的思考过程是这样的:

1.0 版本 DAO 类

  1. 考虑事务 使用同一个连接。
  2. 方法涉及
     update() 可以执行增删改,影响数据库中的数据
     getInstance() 执行查询获取单个记录对象
     getForList()  执行查询获取多个记录对象的集合
     getValue() 可以返回分组函数的结果

2.0 版本 DAO 类 加泛型 优化

思考过程

  1. 在获取单条记录时,CustomerDAOImPlgetCustomerById()调用 getInstance(Connection conn,Class<T> clazz,String sql,Object ...args)
  2. 在获取多条记录时,getAll() 的内部 调用 getForList(Connection conn,Class<T> clazz,String sql,Object ...args)
  3. 他们都在调用时 传入了Customer.class ==>Customer的原型类对象来指明返回什么表的bean 对象
    在内部都使用了这个传入的泛型的指定类型的原型类通过反射封装bean 对象和返回bean对象或者是集合
    在调用时都需要传入 XXX.class 去指明表这个过程显得很重复 所以这里是可以优化的地方

优化方案

  1. BaseDAO 类 ==> 声明成泛型类 BaseDAO
    在实现类继承DAO的时候指定泛型参数的类型

    CustomerDAOImpl extends BaseDAO<Customer> implement CustomerDAO
    
  2. BaseDAO 类的所有泛型方法 ==> 使用BaseDAO 定义的泛型,不声明成泛型方法,方法的形参就不需要传入指定的 XXX.class 用于指定泛型参数

  3. 在BaseDAO中声明泛型参数的属性并使用泛型参数的原型对象进行初始化,所有需要使用到泛型参数的地方引用同一个泛型参数进行反射调用

  4. 泛型参数的定义

    private Class<T> clazz = null;
    
  5. 泛型参数的初始化:

    • 直接初始化
    • 代码块中初始化
    • 构造器中初始化
  6. 为什么泛型参数需要初始化?

  • 分析:
    如果泛型参数不初始化,在 XXXDAOImpl 创建对象并调用 getInstanceByXX()
    方法内部会调用 getInstance(),就会使用这个泛型参数的原型对象来反射使用,未初始化,就会报空指针异常
    泛型参数如何初始化?

  • 分析:
    上述的问题导致空指针异常,所以需要在创建对象之前或者是创建对象的时候要去调用这个getInstanceByXX()
    需要保证我这个泛型参数的引用已经被原型对象初始化了,就是在创建子类对象之前即可

  • 分析结果
    问题解决的突破口:类加载机制的原理
    创建XXXDAOImpl 对象会导致类加载,而且类是一级一级被加载

查阅笔记:

创建一个子类的继承关系时,静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造器的调用顺序如下:

  1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
  3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  4. 父类的构造方法
  5. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
  6. 子类的构造方法

说明:创建子类对象时,调用子类构造器,默认就隐含 调用父类的构造器super() 和 父类的普通代码块和普通属性的执 行。

创建子类对象 要进行类加载 所以先加载父类,若存在多级继承关系,则从最上层父类开始加载父类至子类,类加载必然会执行静态代码块和静态属性,他们优先级一样,看谁在前面,所以的类加载完毕后,再按照 普通代码块,属性,然后再构造器去执行。

public class CodeBlockDetail03 {
    public static void main(String[] args) {
        new BBB();
        /*
        执行顺序是:(1)父类静态相关代码块、属性初始化(2)子类静态相关代码块、属性初始化
        (3)父类的普通代码块、属性初始化(4)父类的构造器初始化(5)子类的普通代码块、属性初始化
        (6)子类的构造器初始化
        
        MMM的静态代码块被执行
        AAA的静态代码块被执行
        AAA的静态属性被执行
        BBB的静态代码块被执行
        hobby普通属性
        MMM的普通代码块
        MMM的构造器被执行
        AAA的普通属性被执行
        AAA的普通代码块被执行
        AAA的构造方法被执行
        BBB的普通代码块被执行
        BBB的构造方法被执行

        

         */
    }
}
class MMM{
    // MMM的普通属性和普通方法
    public String hobby = getHobby();
    public String getHobby(){
        System.out.println("hobby普通属性");
        return "hobby";
    }

    static{
        System.out.println("MMM的静态代码块被执行");
    }

    public MMM(){
        System.out.println("MMM的构造器被执行");
    }
    {
        System.out.println("MMM的普通代码块");
    }



}
class AAA extends MMM {
    // 静态代码块
    static {
        System.out.println("AAA的静态代码块被执行");
    }

    public static int age = getAge();

    public static int getAge() {
        System.out.println("AAA的静态属性被执行");
        return 100;
    }

    public String name = getName();

    {
        System.out.println("AAA的普通代码块被执行");
    }

    public String getName() {
        System.out.println("AAA的普通属性被执行");
        return "hhh";
    }

    public AAA() {
        System.out.println("AAA的构造方法被执行");
    }
}

class BBB extends AAA {

    {
        System.out.println("BBB的普通代码块被执行");
    }

    static {
        System.out.println("BBB的静态代码块被执行");
    }

    public BBB() {
        // 隐含调用父类的构造器
        // super()
        // 本类的普通代码块被执行
        System.out.println("BBB的构造方法被执行");
    }

}


查阅csdn

原来卡在这

👉 点击跳转 ParameterizedType详解 https://blog.csdn.net/candyguy242/article/details/102940035

理解代码并代码验证

class Boy{

}
class Girl{

        }
class A<T,U>{
    // 定义泛型参数
    private Class<T> type = null;
    // 在代码块中初始化泛型参数
    {   // 获取子类的原型对象
        Class cls = this.getClass();
        System.out.println("当前对象:"+this.getClass());

        // 获取当前子类的直接父类的类型
        // Type接口对象必须准确反映源代码中使用的实际类型参数
        Type gsc = cls.getGenericSuperclass();
        System.out.println(gsc); // com.zq.dao.A<com.zq.dao.Boy, com.zq.dao.Girl>

        // 获取父类泛型的 参数化类型
        ParameterizedType parameterizedType = (ParameterizedType) gsc;
        // 获取父类泛型参数化类型 的所有 参数对象
        Type[] types = parameterizedType.getActualTypeArguments();
        // 获取参数对象
        Type type1 = types[0];
        // class com.zq.dao.Boy 地址:21282042
        System.out.println("泛型的参数对象:"+type1+"地址:"+type1.hashCode());
        // 初始化赋值
        this.type = (Class<T>) type1;

    }

        }

class B extends A<Boy,Girl>{
    B(){

    }

    public static void main(String[] args) {
        new B();

    }

}

当前对象:class com.zq.dao.B
com.zq.dao.A<com.zq.dao.Boy, com.zq.dao.Girl>
泛型的参数对象:class com.zq.dao.Boy地址:21282042

豁然开朗

所以子类创建对象,导致的类一级一级的加载,根据类加载顺序,使得这个原型对象在子类创建对象之前初始化即可

	private Class<T> type;

	// 获取T的Class对象,获取泛型的类型,泛型是在被子类继承时才确定
	public BaseDao() {
		// 获取子类的类型, 
        // 子类创建对象,调用构造方法,导致类加载,根据类加载原则,
        // 父类构造器比子类构造器先加载,并且目前的对象是子类对象,this的运行类型就是子类对象,获取的也就是
        // 子类的Class 对象
		Class clazz = this.getClass();
		// 获取父类的类型
		// getGenericSuperclass()用来获取当前类的父类的类型
		// ParameterizedType表示的是带泛型的参数化类型==> 获取的是 class  A<T,E,F> 像这样尖括号中有参数的泛型
		ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
		// 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的参数类型:通俗的讲就是获得 <A,B> 获得尖括号中的东西
		// 这个方法会返回一个Type的数组==>  尖括号中的东西不止一个
		Type[] types = parameterizedType.getActualTypeArguments();
		// 获取具体的泛型的类型的对象并赋值给 type ,让 type 初始化,后面每个方法就可以使用这个 type 来通过反射来创建对象,封装数据返回s
		this.type = (Class<T>) types[0];
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值