反射基础(二)模拟Hibernate

打开这篇文章时,相信大家对反射有了一定的了解了,如果对反射还有些陌生,大家可以点击查看反射基础(一),这篇文章具体是说明反射如何使用的,使用反射的好处是什么,模拟Hibernate来实现不用手写sql,只需一个方法便能查询一张表和插入一条数据。

1.获取类名和方法名

1.1 获取类名

Class<?> c = Class.forName("com.chao.entity.Emp");

运行结果:Emp

mysql数据库中是不区分大小写的,所以emp和Emp均可用。

1.2 获取属性名

  1. c.getDeclaredFields()得到访问权限+数据类型+包名.类名.属性名
    遍历,运行结果:
    private java.lang.Integer com.chao.entity.Emp.empno这显然不是我们想要的

  2. f.getName()仅得到属性名
    运行结果:
    在这里插入图片描述

    因为数据库的命名风格(多个字母下划线隔开t_user)和java驼峰式命名完全是两种风格,字段名和类名不一样是常见的事情,我们会采用自定义注解来解决这一问题。

  3. 应用场景

    Class<?> c = Class.forName("com.chao.entity.Emp");
    Field[] fs = c.getDeclaredFields();
    for (Field f : fs) {
        System.out.println(f.getName());
    }
    

2.自定义注解

我们都知道注解,使用注解大大提高了我们的开发效率,最常见的注解@Override,也是我认识的第一个注解,@Override它的作用仅限于方便阅读,在运行便会消失。不要问我怎么知道的,也不用背过,我带着大家看下我知道的原因。

2.1 查看@Override注解

2.1.1 查看注解

Ctrl + 鼠标左键@Override

在这里插入图片描述
此处仅包含两个注解
@Target(ElementType.METHOD)目标在哪里,即作用域
@Retention(RetentionPolicy.SOURCE)保留到哪里,

  1. @Target(ElementType.METHOD)主要是在说使用到类上,方法上,构造方法上属性上等等。
  2. @Retention(RetentionPolicy.SOURCE)刚好对应java代码在计算机中的三个阶段,如下表:
第一阶段第二阶段第三阶段
源代码阶段Class类对象阶段Runtime运行时阶段
javac编译成class文件将class文件加载到内存可以创建对象,调用方法

2.1.2 查看源码

@Target(ElementType.METHOD)作用目标在哪里,就是可以标注在哪里

  1. Ctrl + 鼠标左键点击 Target进去便可以看到以下内容,在这里插入图片描述
  2. Ctrl + 鼠标左键点击 ElementType你就可以看到以下内容
    在这里插入图片描述
    它是枚举类型,表示各个类型的作用域。

    这样你就可以自己查看任何注解了。

2.2 自定义类注解

自定义类注解,顾名思义,作用在类上即@Target(ElementType.TYPE)
在运行时与数据库建立动态的连接,@Retention(RetentionPolicy.RUNTIME)
需要添加新的属性名,来标记与数据库名一一对应,我们需要在注解里面声明参数,即String name();,这样我们一个类注解已经简历完成了。
如下代码所示:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author : WXC
 * date    : 2020/9/1 20:19
 * desc    :- 类注解,解决类名与库名不一致问题
 * 注解的本质是clss文件
 */
//声明注解的作用域在哪里,即标注在哪里
@Target(ElementType.TYPE)
//声明注解可以保留到哪个阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    //在注解里面声明参数:用抽象方法的形式
    String name();
}

2.3 自定义方法注解

既然类注解,已经定义完成了,方法注解我们依然需要考虑三个问题:
目标在哪里
保留到哪里
注解需要参数吗
带着问题,很快就有了答案,话不多说,直接附上代码

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String name();
}

3.使用自定义注解

使用注解超级简单,学过Spring等框架的,无非就是标记和使用,创建自定义注解框架已经帮你做好了,但是使用我们的自定义注解也就是,创建自定义注解,标注自定义注解,获取自定义注解的值。

3.1 在类上标注注解

在自定义注解的作用范围内,正确的使用注解,name是在注解里面使用参数,类型是String(注解里声明的String name();),所以在注解参数中为name = "字符串"

在这里插入图片描述

3.2获取类上的注解

Class<?> c = Class.forName("com.chao.entity.Emp");
Table table = c.getDeclaredAnnotation(Table.class);
System.out.println(table.name());

运行结果:
在这里插入图片描述

3.3 标记属性注解和获取属性上的注解

属性上的注解类上的注解大同小异,无非就是标记和获取这两块,话不多说,让你一览无遗:

  1. 标记注解
    在这里插入图片描述

  2. 获取注解,也就两行代码

    //获取属性名上的注解
    Column column = f.getAnnotation(Column.class);
    //获取注解里面的值 column.name()
    System.out.println(column.name());
    
  3. 其实我们不清楚那个属性有注解,哪个属性没有注解,所以我们只能遍历全部,通过查看注解是否为空,来确定该属性是否有注解

    Class<?> c = Class.forName("com.chao.entity.Emp");
    //获取所有的属性
    Field[] fs = c.getDeclaredFields();
    //遍历属性名
    for (Field f : fs) {
        //判断该属性是否有注解
        Column column = f.getAnnotation(Column.class);
        //不存在注解时,则获取属性名
        if (column == null) {
            System.out.println(f.getName());
        } else {
            //存在注解,则获取注解值
            System.out.println(column.name());
        }
    }
    

    运行结果:在这里插入图片描述

    我们确实获取到了属性名上的注解了,目前我们已经可以获取类上和属性上的注解,这样我们就可以使用自定义注解来保持数据库和属性名的一致了,下面就带着大家封装一个查询任意数据库中任意一张表的方法吧。

4.查询任意一张表

  1. 封装方法

    根据传入的class对象读取数据库一张表的所有数据,返回list集合
    反射出类名当做数据库的表名,所有的属性名当作列明来拼接处一个sql

    public class DBUtils {
       //我使用的是连接池,你们自行导入druid连接池和mysql-connector-java的jar包
        private static DruidDataSource dataSource = new DruidDataSource();
    	//连接数据库
        static {
            dataSource.setUrl("jdbc:mysql:///emp_base");
            dataSource.setUsername("root");
            dataSource.setPassword("root");
        }
    
        /**
         * 根据传入的class对象读取数据库一张表的所有数据,返回list集合
         * 反射出类名当做数据库的表名,所有的属性名当作列明来拼接处一个sql
         *
         * @param c   class对象
         * @param <T> 泛型
         * @return list<T>
         */
        public static <T> List<T> getAll(Class c) {
            //1.声明一条sql语句
            StringBuilder sql = new StringBuilder("select ");
            //2.反射出所有的属性名当作列明拼接到sql中
            Field[] fs = c.getDeclaredFields();
            for (Field f : fs) {
                //如果有注解,就应该获取注解里面的值当作列名,反之则用属性名
                //一个属性可以标记多个注解,但是不可以标记重复的注解
                Column column = f.getDeclaredAnnotation(Column.class);
                //如果没有注解时,column为空,正常拼接
                if (column == null) {
                    sql.append(f.getName()).append(",");
                } else {
                    //存在注解时,则获取注解里面值进行拼接
                    sql.append(column.name()).append(",");
                }
            }
            //删除指定字符
            sql.deleteCharAt(sql.length() - 1);
            //追加from关键字
            sql.append(" from ");
            //反射出类名当表名
            //获取类上的注解
            Table table = (Table) c.getDeclaredAnnotation(Table.class);
            if (table == null) {
                sql.append(c.getSimpleName());
            } else {
                sql.append(table.name());
            }
            System.out.println(sql);
    
            //3.连接数据库,执行查询
            Connection conn = null;
            Statement sta = null;
            ResultSet rs = null;
            List<T> tList = new ArrayList<>();
            try {
                conn = dataSource.getConnection();
                sta = conn.createStatement();
                rs = sta.executeQuery(String.valueOf(sql));
                while (rs.next()) {
                    //遍历数据遇到问题了,不清楚数据库中的字段类型,也不知道类中属性类型,则可以利用反射来处理
                    //用反射来创建数据对象
                    T t = (T) c.newInstance();
                    //给t设置数据
                    for (Field f : fs) {
                        //是否取消访问检查
                        f.setAccessible(true);
                        //给t(对象) 设置属性(f)的值为rs.getObject(f.getName())
                        //rs.getObject(f.getName())
                        Column column = f.getDeclaredAnnotation(Column.class);
                        if (column == null) {
                            f.set(t, rs.getObject(f.getName()));
                        } else {
                            f.set(t, rs.getObject(column.name()));
                        }
                    }
                    tList.add(t);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
            return tList;
        }
    }
    
  2. 测试方法1
    public static void main(String[] args) {
        List<Emp> empList = DBUtils.getAll(Emp.class);
        for (Emp emp : empList) {
            System.out.println(emp);
        }
    }
    
    运行结果:
    在这里插入图片描述
  3. 测试方法2
  • 数据库表:
    在这里插入图片描述

  • 实体类
    在这里插入图片描述

    创建了User实体类,里面有无参和全参的构造方法,get和set方法,tostring方法,大家可以看到,表的字段与实体类的属性不一致,所以加上了注解。

  • 测试代码

    public static void main(String[] args) {
        List<User> userList = DBUtils.getAll(User.class);
        for (User user : userList) {
            System.out.println(user);
        }
    }
    
  • 运行结果
    在这里插入图片描述

运行结果中红色是因为使用了SSL,
我们只需要在后面url后面拼接?useSSL=false便可以解决这个问题。

5.插入数据

插入数据的代码直接附上

public static int insert(
    //获取Class对象
    Class c = obj.getClas
    //拼接sql
    StringBuilder sql = n
    //获取表明  注意是否有注解
    Table table = (Table)
    //不存在注解时
    if (table == null) {
        sql.append(c.getS
    } else {
        sql.append(table.
    }
    sql.append(" (");
    //拼接字段名
    Field[] fs = c.getDec
    for (Field f : fs) {
        f.setAccessible(t
        Column column = f
        if (column == nul
            sql.append(f.
        } else {
            sql.append(co
        }
        sql.append(",");
    }
    Field[] aaa = c.getDe
    //删除指定字符
    sql.deleteCharAt(sql.
    sql.append(") values 
    //拼接?
    for (int i = 0; i < f
        sql.append("?,");
    }
    //删除指定字符,即最后一个','号
    sql.deleteCharAt(sql.
    sql.append(")");
    Connection conn = nul
    PreparedStatement pst
    System.out.println(sq
    //添加时受影响的行数
    int count = 0;
    try {
        conn = dataSource
        psta = conn.prepa
        //针对 ? 设置值
        for (int i = 0; i
            psta.setObjec
        }
        //执行sql
        count = psta.exec
    } catch (SQLException
        e.printStackTrace
    } catch (IllegalAcces
        e.printStackTrace
    } finally {
        //记得关闭资源哦
    }
    return count;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值