文章目录
打开这篇文章时,相信大家对反射有了一定的了解了,如果对反射还有些陌生,大家可以点击查看反射基础(一),这篇文章具体是说明反射如何使用的,使用反射的好处是什么,模拟Hibernate来实现不用手写sql,只需一个方法便能查询一张表和插入一条数据。
1.获取类名和方法名
1.1 获取类名
Class<?> c = Class.forName("com.chao.entity.Emp");
运行结果:Emp
mysql数据库中是不区分大小写的,所以emp和Emp均可用。
1.2 获取属性名
-
c.getDeclaredFields()
得到访问权限+数据类型+包名.类名.属性名
遍历,运行结果:
private java.lang.Integer com.chao.entity.Emp.empno
这显然不是我们想要的 -
f.getName()
仅得到属性名
运行结果:
因为数据库的命名风格(多个字母下划线隔开t_user)和java驼峰式命名完全是两种风格,字段名和类名不一样是常见的事情,我们会采用自定义注解来解决这一问题。
-
应用场景
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)
保留到哪里,
@Target(ElementType.METHOD)
主要是在说使用到类上,方法上,构造方法上属性上等等。@Retention(RetentionPolicy.SOURCE)
刚好对应java代码在计算机中的三个阶段,如下表:
第一阶段 | 第二阶段 | 第三阶段 |
---|---|---|
源代码阶段 | Class类对象阶段 | Runtime运行时阶段 |
javac编译成class文件 | 将class文件加载到内存 | 可以创建对象,调用方法 |
2.1.2 查看源码
@Target(ElementType.METHOD)
作用目标在哪里,就是可以标注在哪里
Ctrl + 鼠标左键点击 Target
进去便可以看到以下内容,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 标记属性注解和获取属性上的注解
属性上的注解和类上的注解大同小异,无非就是标记和获取这两块,话不多说,让你一览无遗:
-
标记注解
-
获取注解,也就两行代码
//获取属性名上的注解 Column column = f.getAnnotation(Column.class); //获取注解里面的值 column.name() System.out.println(column.name());
-
其实我们不清楚那个属性有注解,哪个属性没有注解,所以我们只能遍历全部,通过查看注解是否为空,来确定该属性是否有注解
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.查询任意一张表
- 封装方法
根据传入的class对象读取数据库一张表的所有数据,返回list集合
反射出类名当做数据库的表名,所有的属性名当作列明来拼接处一个sqlpublic 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; } }
- 测试方法1
运行结果:public static void main(String[] args) { List<Emp> empList = DBUtils.getAll(Emp.class); for (Emp emp : empList) { System.out.println(emp); } }
- 测试方法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;
}