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;