目标
- 模拟JPA的实现方式,封装底层的jdbc操作,让开发者专心于Java程序的编写而不是SQL语句
- 搞情况JPA与jdbc的关系,摸清Java程序与数据库交互的具体流程
- 练习注解与反射的配合使用
方法
1.抛入需要的jar包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- JdbcTemplate依赖的jar包,使用JdbcTemplate简化jdbc操作 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.9</version>
</dependency>
<!-- Druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
<!-- MySQL数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
2.要实现一个JPA规范,我们应该明白JPA处于Java程序与数据库交互的哪个阶段:Java程序 –> JPA –> JDBC –> 数据库驱动 –> 数据库;而JPA具体的作用就是建立实体类与表之间的关系,通过操作实体类就能将数据持久化到数据库,节省我们书写SQL的时间,以及将数据库字段与实体类属性一一对应进行转化的繁杂,大大提升开发效率;
3.从第二点,JPA所处的阶段位于Java程序和JDBC之间,所以JPA终究还是要靠jdbc与数据库进行连接,实际上它的底层依然要依靠jdbc,所以我们才引入了JdbcTemplate;
4.由于底层还是用了JDBC,所以写代码之前我们应该对JDBC与数据库建立连接的具体流程有个大概的流程,请看下图:
5.所以底层我们还是需要写SQL,当然有了这个demo我们就只要写一次SQL,以后遇到相同的操作就不用再写一遍SQL了,没错JPA的其中一个优势就体现在这了;
代码
①@Table注解
1
2
3
4
5
6
7
8
9
/**
* 为实体类指定与它关联的表的名称
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String value() default "";
}
②准备实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@Table("t_user")
public class User {
private Integer id;
private String name;
private Integer age;
public User() {
}
public User(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
③准备数据表
1
2
3
4
5
6
7
CREATE TABLE t_user
(
`id` INT NOT NULL auto_increment,
`name` VARCHAR(50),
`age` TINYINT,
PRIMARY KEY(id)
)
④BaseDao
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class BaseDao<T> {
private final JdbcTemplate jdbcTemplate = JdbcUtil.getJdbcTemplate();
private Class<T> actualTypeArgument;
private final HashMap<String, Object> hashMap = new HashMap<>(50);
public BaseDao() {
// 0.获取实际类型参数
ParameterizedType genericSuperclass = (ParameterizedType)this.getClass().getGenericSuperclass();
Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments();
actualTypeArgument = (Class<T>) actualTypeArguments[0];
}
public int add(T obj){
// 1.获取实际类型参数的属性名和属性值,并存放在hashMap中(由于使用的泛型,显然我们需要使用反射获取运行时类型对象,以此获取对象的属性名和属性个数)
Field[] declaredFields = actualTypeArgument.getDeclaredFields();
for (Field field : declaredFields) {
// 通过反射获取对象的私有属性值,因而应该关闭Java语言的安全检查
field.setAccessible(true);
try {
hashMap.put(field.getName(), field.get(obj));
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 2.拼装sql
StringBuffer sqlBuffer1 = new StringBuffer("insert into " + actualTypeArgument.getAnnotation(Table.class).value() + "(");
StringBuffer sqlBuffer2 = new StringBuffer("values(");
// 3.获取属性名的集合
Object[] keyArray = hashMap.keySet().toArray();
// 4.获取属性个数
int size = hashMap.size();
for (int i = 0; i < size-1; i++) {
sqlBuffer1.append("`"+keyArray[i]+"`"+",");
sqlBuffer2.append("?,");
}
sqlBuffer1.append("`"+keyArray[size-1]+"`)");
sqlBuffer2.append("?)");
String sql = sqlBuffer1.toString()+sqlBuffer2.toString();
// 5.获取对象属性值的集合
Object[] values = hashMap.values().toArray();
// 6.执行sql
return jdbcTemplate.update(sql, values);
}
}
使用时,让XXXDao继承BaseDao
结论与发现
1.这个小demo只实现了insert操作,像创建数据库,查询、更新、删除都可以通过这种方式实现的
2.核心代码
1
2
3
ParameterizedType genericSuperclass = (ParameterizedType)this.getClass().getGenericSuperclass();
Type[] actualTypeArguments = genericSuperclass.getActualTypeArguments();
actualTypeArgument = (Class<T>) actualTypeArguments[0];
这段代码展示了:如何获取父类的实际类型参数的Class对象,比如UserDao extends BaseDao<User>,实际类型参数就是User;要做到这步我们需要做到下面几步:
①首先我们要在父类中获取子类的Class对象,众所周知子类在初始化时实际上隐式的将this对象传递给了父类,所以在父类构造器中使用的this实际上是子类对象,这样我们就可以通过this.getClass()获取到
②如何获取泛型超类,Class.getGenericSuperclass()就可以获取
③如何获取实际类型参数的Class对象,ParameterizedType.getActualTypeArguments(),这一这是一个Type类型的数组,而Class类是它的实现类,而里面装的就是实际类型参数的Class对象,第一个数组元素就是我们要的啦