首页 JPA的小demo
文章
取消

JPA的小demo

目标

  • 模拟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的时间,以及将数据库字段与实体类属性一一对应进行转化的繁杂,大大提升开发效率;
JPA所处的阶段

3.从第二点,JPA所处的阶段位于Java程序和JDBC之间,所以JPA终究还是要靠jdbc与数据库进行连接,实际上它的底层依然要依靠jdbc,所以我们才引入了JdbcTemplate;

4.由于底层还是用了JDBC,所以写代码之前我们应该对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对象,第一个数组元素就是我们要的啦

本文由作者按照 CC BY 4.0 进行授权