Spring Hibernate 5 快速入门(一) 最基本的Entity和增查改删

0.35字数 1782阅读 1486

1. 概述

所有的代码都保证是可运行的完整project, 代码分享在github.com, 平时工作中也可以作为模板代码Ctrl+c用.
https://github.com/ZhongjunTian/spring-hibernate-examples
本章内容在basic-hibernate文件夹下, 总共大约100行代码.
前提:
需熟悉Java, 了解关系型数据库(RDBMS)的基本常识, 使用过Spring Boot.
Java 8, Maven 3, Eclipse或者Intellij

2. 优 & 劣

Hibernate是Java世界最流行的ORM框架之一, 另一个流行的ORM框架是MyBatis.
相对来说, MyBatis更轻量, 简单, 灵活. 而Hibernate入门就难许多, 优秀的Hibernate教程也很少. 但从功能上来说Hibernate更强大, 使用得当的话可以用最少的代码做最多的事情.
Hibernate对比Mybatis:https://www.zhihu.com/question/21104468
Hibernate的优缺点:https://www.zhihu.com/question/21607222

3. Hibernate的优势

首先ORM(Object-relational mapping) = 对象关系映射, 简单的说就是帮你写SQL查询语句里面的废话

举个栗子
想象一下我们有如下一个表

CREATE TABLE PERSON (
    id bigint not null,
    name varchar(255) not null,
    address varchar(255) not null
);

3.1 不用Hibernate, 用JDBC的读取数据

那么如果你要查询数据, 没有ORM的话, 你就要写这个查询语句
select * from PERSON , 并且建立JDBC连接, 执行Statement, 获取ResultSet, 并且把每一列数据从String转换成int, double, String, Date等, 最后放进Java的对象里面.
那如果是UPDATE或者INSERT, 并且要连表查询呢, 那就更麻烦了, 并且代码很难复用.

public static void main(String[] args) {
        String driver = "com.mysql.jdbc.Driver";
        String dbName = "spring";
        String passwrod = "root";
        String userName = "root";
        String url = "jdbc:mysql://localhost:3308/" + dbName;
        String sql = "select * from users";

        try {
            Class.forName(driver);
            try (
                    Connection conn = DriverManager.getConnection(url, userName,passwrod);
                    PreparedStatement ps = conn.prepareStatement(sql);
                    ResultSet rs = ps.executeQuery();
            ) {
                while (rs.next()) {
                    System.out.println("id : " + rs.getInt(1) + " name : "
                            + rs.getString(2) + " address : " + rs.getString(3));
                }

            }catch (SQLException e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
}

3.2 用Hibernate的读取数据

但是一旦有了Hibernate以及Spring Reposiroty, 你只要在class Person里面用java注释定义好, 那么你只用一行代码就能完成上面所有事情

List<Person> persons = personRepository.findAll(); //这么简单? 吓死本宝宝了 :)

4.创建我们的第一个Entity

废话少说, 假设我们有如下一个表, 这个表基本上涵盖了常用的数据结构
源代码总共不到100行代码, 下载代码 https://github.com/ZhongjunTian/spring-hibernate-examples
用Intellij或者Eclipse打开 /basic-hibernate运行main函数, 或者
命令行进入 /basic-hibernate 文件夹之后 mvn spring-boot:run 即可运行例程, console会输出读写信息。

4.1 表的定义

定义在项目的resources/schma-h2.sql当中, 运行时Spring Boot会自动启动java迷你数据库h2, 并且执行这个文件.

CREATE TABLE Person (
    id BIGINT GENERATED BY DEFAULT AS IDENTITY,
    user_name varchar(255) not null,
    birth_date DATETIME null,
    money decimal null,
    gender char(1) null,
    clob clob null
);

4.2 定义Entity

在Hibernate的世界中, 每一个表都要有一个对应的Entity, 而这个对应的Entity也就是Hibernate的核心. 所谓ORM也就是从Java Object映射到关系数据库(RDMS). 所以这个是非常重要的.
下面这个Entity对应上面的表, 覆盖了所有常用标注.

@Entity
public class Person {
    @Id
    @GeneratedValue
    Long id;

    @Column(name = "userName")
    String name;

    @Temporal(TemporalType.TIMESTAMP)
    Date birthDate;

    BigDecimal money;

    Character gender;

    @Transient
    boolean gay;

    @Lob
    String clob;
}

重要的标注有

@Entity 告诉Hibernate这个类对应着数据库的一个表, 默认Hibernate会认为表的名字与这个class的名字一样, Hibernate核心代码会扫描整个class.

@Id 标明这个是数据库表的主键, 一般用Long类型即可(一定不要使用int long等类型).
使用Spring的情况下, 通常我们很少会见到传统数据库的insert与update操作, 我们通常会使用repository.save(entity). 那Hibernate如何判断执行Insert还是update操作呢? 很简单如果一个entity.id == null说明需要insert数据, 反之则为update操作(update需要主键).

@GeneratedValue 标明主键的生成方式, 这里我们用的默认的值, 也就是主键由数据库自动生成 (对应mysql里面的auto increment选项). 当hibernate执行SQL语句的时候并不会插入id, 而是由数据库自动增加. 另外还有一种高性能的id生成策略叫hi lo, 可以支持批处理, 这里不赘述.

@Temporal(TemporalType.TIMESTAMP) 标明Date在数据库里面的精度, 因为我们用的是java.util.Date类型. 这个是精度非常高的类型, 可以表示yyyy/MM/dd HH:mm:sss. 而在数据库对应的列可能会有 Date, Time, TimeStamp三种不同的类型, 这里只要如实填写即可. 其中Date只有日期, Time只有时间, TimeStamp两者都有. 比如设置为TimeStamp.Date, 那么这个Date birthDate为 2000/1/1 12:22:22, Hibernate也只会保存前面的2000/1/1到数据库.

常用的标注有

@Column 在java 类里面变量与表里面的某一列的名字不一样的时候使用. 这里 @Column(name = "userName"), 在Hibernate生成SQL语句的时候, 就会使用user_name而不是name;(这里Spring Boot里面默认把java的驼峰命名userName转换成了数据库最流行的user_name风格)
没有@Column的时候Hibernate会默认Java类成员的名字与表里面的名字是一样的. 比如id 对应表里面 id, birthDate 对应表里面 birth_date.

@Transient 也是一个经常被用到的标注. Transient在英文中的意思是'短暂的', 它的反义词刚好是Persistent'持久的, 持续化的'. 正如其名, 被@Transient标注的成员不会被Hibernate管理, 也就是无法被Hibernate保存到数据库, 也不会从数据库里面读取. (类似于Jackson里面的@JsonIgnore). 只是Java类里面的一个普通成员, Hibernate会无视它.

@Lob lob是large object的意思, 也就是超大的object, 比如图片,文件,超长的String等等, 统统都可以简单的塞进数据库. 我这里用的java类型是String clob, 其实也可以是 byte[], char[]等等.

数据库类型映射到Java类型

我们可以仔细对比一下3.1的表的定义和3.2的entity定义, 我们把数据库里面bigint, varchar, datetime等类型映射成为了Java里面的Long, String, Date.
这里有两个需要注意的地方, id一定不能用long, 必须用Long.
其他的成员如果在数据库里面有not null限制, 那么可以用 int/long/float/double这种原始数据类型, 否则建议用Integer/Long这种 (比如3.1的balance decimal null)

5. 读写数据库

首先我们定义一个Spring的数据仓库. 直接创建如下接口即可. 其中<Person, Long>意思是Entity为Account, 并且id的数据类型为Long. 当Spring JPA的核心代码扫描到Person Repository这个接口之后, 会自动创建一个SimpleJpaRepository的实例.

public interface PersonRepository extends JpaRepository<Person,Long> {
}

下面的personRepository其实就是个SimpleJpaRepository的实例.
剩下的代码就很简单了, 增查改删一气呵成, 毫无废话. 强烈建议读者在IDE里面运行完整版的代码, 可以自己试着改一改, 玩一玩.
Talk is cheap, show you the code.

   @Autowired
    PersonRepository personRepository;

    public void run(String... strings) throws Exception {
        System.out.println("Start!");
        //增
        Person person = Person.createAccount();
        personRepository.save(person);
        System.out.println();

        //查
        Person acct = personRepository.findAll().get(0);
        System.out.println(String.format("Person after creation: %s, %s, %s, %s, %s, %s, %s",
                acct.id, acct.name, acct.birthDate, acct.money, acct.gender, acct.gay, acct.clob));

        //改
        acct.name = "newName";
        personRepository.save(acct);
        acct = personRepository.findAll().get(0);
        System.out.println("UserName after update: "+acct.name);

        //删
        personRepository.delete(acct);
        List<Person> people = personRepository.findAll();
        System.out.println("Size after deletion: "+ people.size());

    }

personRepository.findAll(); 就相当于select * from person;
personRepository.save(account);就能insert或者update表. 当account里面的id == null的时候是insert 相当于执行SQL语句insert into person ... . 不为null, 比如 id == 1 的时候对应update, 相当于执行update person ... where id=1
personRepository.delete(acct) 根据entity的id做删除操作, 比如 acct.id等于1, 那么执行就的是 delete from account where id=1;

雪地打滚360度跪求读者提意见!

推荐阅读更多精彩内容