1. 概述
所有的代碼都保證是可運(yùn)行的完整project, 代碼分享在github.com, 平時工作中也可以作為模板代碼Ctrl+c用.
https://github.com/ZhongjunTian/spring-hibernate-examples
本章內(nèi)容在basic-hibernate文件夾下, 總共大約100行代碼.
前提:
需熟悉Java, 了解關(guān)系型數(shù)據(jù)庫(RDBMS)的基本常識, 使用過Spring Boot.
Java 8, Maven 3, Eclipse或者Intellij
2. 優(yōu) & 劣
Hibernate是Java世界最流行的ORM框架之一, 另一個流行的ORM框架是MyBatis.
相對來說, MyBatis更輕量, 簡單, 靈活. 而Hibernate入門就難許多, 優(yōu)秀的Hibernate教程也很少. 但從功能上來說Hibernate更強(qiáng)大, 使用得當(dāng)?shù)脑捒梢杂米钌俚拇a做最多的事情.
Hibernate對比Mybatis:https://www.zhihu.com/question/21104468
Hibernate的優(yōu)缺點:https://www.zhihu.com/question/21607222
3. Hibernate的優(yōu)勢
首先ORM(Object-relational mapping) = 對象關(guān)系映射, 簡單的說就是幫你寫SQL查詢語句里面的廢話
舉個栗子
想象一下我們有如下一個表
CREATE TABLE PERSON (
id bigint not null,
name varchar(255) not null,
address varchar(255) not null
);
3.1 不用Hibernate, 用JDBC的讀取數(shù)據(jù)
那么如果你要查詢數(shù)據(jù), 沒有ORM的話, 你就要寫這個查詢語句
select * from PERSON
, 并且建立JDBC連接, 執(zhí)行Statement, 獲取ResultSet, 并且把每一列數(shù)據(jù)從String轉(zhuǎn)換成int, double, String, Date
等, 最后放進(jìn)Java的對象里面.
那如果是UPDATE或者INSERT, 并且要連表查詢呢, 那就更麻煩了, 并且代碼很難復(fù)用.
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的讀取數(shù)據(jù)
但是一旦有了Hibernate以及Spring Reposiroty, 你只要在class Person里面用java注釋定義好, 那么你只用一行代碼就能完成上面所有事情
List<Person> persons = personRepository.findAll(); //這么簡單? 嚇?biāo)辣緦殞毩?:)
4.創(chuàng)建我們的第一個Entity
廢話少說, 假設(shè)我們有如下一個表, 這個表基本上涵蓋了常用的數(shù)據(jù)結(jié)構(gòu)
源代碼總共不到100行代碼, 下載代碼 https://github.com/ZhongjunTian/spring-hibernate-examples
用Intellij或者Eclipse打開 /basic-hibernate運(yùn)行main函數(shù), 或者
命令行進(jìn)入 /basic-hibernate 文件夾之后 mvn spring-boot:run 即可運(yùn)行例程, console會輸出讀寫信息。
4.1 表的定義
定義在項目的resources/schma-h2.sql當(dāng)中, 運(yùn)行時Spring Boot會自動啟動java迷你數(shù)據(jù)庫h2, 并且執(zhí)行這個文件.
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的世界中, 每一個表都要有一個對應(yīng)的Entity, 而這個對應(yīng)的Entity也就是Hibernate的核心. 所謂ORM也就是從Java Object映射到關(guān)系數(shù)據(jù)庫(RDMS). 所以這個是非常重要的.
下面這個Entity對應(yīng)上面的表, 覆蓋了所有常用標(biāo)注.
@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;
}
重要的標(biāo)注有
@Entity
告訴Hibernate這個類對應(yīng)著數(shù)據(jù)庫的一個表, 默認(rèn)Hibernate會認(rèn)為表的名字與這個class的名字一樣, Hibernate核心代碼會掃描整個class.
@Id
標(biāo)明這個是數(shù)據(jù)庫表的主鍵, 一般用Long類型即可(一定不要使用int long等類型).
使用Spring的情況下, 通常我們很少會見到傳統(tǒng)數(shù)據(jù)庫的insert與update操作, 我們通常會使用repository.save(entity). 那Hibernate如何判斷執(zhí)行Insert還是update操作呢? 很簡單如果一個entity.id == null說明需要insert數(shù)據(jù), 反之則為update操作(update需要主鍵).
@GeneratedValue
標(biāo)明主鍵的生成方式, 這里我們用的默認(rèn)的值, 也就是主鍵由數(shù)據(jù)庫自動生成 (對應(yīng)mysql里面的auto increment選項). 當(dāng)hibernate執(zhí)行SQL語句的時候并不會插入id, 而是由數(shù)據(jù)庫自動增加. 另外還有一種高性能的id生成策略叫hi lo, 可以支持批處理, 這里不贅述.
@Temporal(TemporalType.TIMESTAMP)
標(biāo)明Date在數(shù)據(jù)庫里面的精度, 因為我們用的是java.util.Date類型. 這個是精度非常高的類型, 可以表示yyyy/MM/dd HH:mm:sss. 而在數(shù)據(jù)庫對應(yīng)的列可能會有 Date, Time, TimeStamp三種不同的類型, 這里只要如實填寫即可. 其中Date只有日期, Time只有時間, TimeStamp兩者都有. 比如設(shè)置為TimeStamp.Date, 那么這個Date birthDate為 2000/1/1 12:22:22, Hibernate也只會保存前面的2000/1/1到數(shù)據(jù)庫.
常用的標(biāo)注有
@Column
在java 類里面變量與表里面的某一列的名字不一樣的時候使用. 這里 @Column(name = "userName")
, 在Hibernate生成SQL語句的時候, 就會使用user_name而不是name;(這里Spring Boot里面默認(rèn)把java的駝峰命名userName轉(zhuǎn)換成了數(shù)據(jù)庫最流行的user_name風(fēng)格)
沒有@Column
的時候Hibernate會默認(rèn)Java類成員的名字與表里面的名字是一樣的. 比如id 對應(yīng)表里面 id, birthDate 對應(yīng)表里面 birth_date.
@Transient
也是一個經(jīng)常被用到的標(biāo)注. Transient在英文中的意思是'短暫的', 它的反義詞剛好是Persistent'持久的, 持續(xù)化的'. 正如其名, 被@Transient
標(biāo)注的成員不會被Hibernate管理, 也就是無法被Hibernate保存到數(shù)據(jù)庫, 也不會從數(shù)據(jù)庫里面讀取. (類似于Jackson里面的@JsonIgnore
). 只是Java類里面的一個普通成員, Hibernate會無視它.
@Lob
lob是large object的意思, 也就是超大的object, 比如圖片,文件,超長的String等等, 統(tǒng)統(tǒng)都可以簡單的塞進(jìn)數(shù)據(jù)庫. 我這里用的java類型是String clob, 其實也可以是 byte[], char[]等等.
數(shù)據(jù)庫類型映射到Java類型
我們可以仔細(xì)對比一下3.1的表的定義和3.2的entity定義, 我們把數(shù)據(jù)庫里面bigint, varchar, datetime等類型映射成為了Java里面的Long, String, Date.
這里有兩個需要注意的地方, id一定不能用long, 必須用Long.
其他的成員如果在數(shù)據(jù)庫里面有not null限制, 那么可以用 int/long/float/double這種原始數(shù)據(jù)類型, 否則建議用Integer/Long這種 (比如3.1的balance decimal null
)
5. 讀寫數(shù)據(jù)庫
首先我們定義一個Spring的數(shù)據(jù)倉庫. 直接創(chuàng)建如下接口即可. 其中<Person, Long>
意思是Entity為Account, 并且id的數(shù)據(jù)類型為Long. 當(dāng)Spring JPA的核心代碼掃描到Person Repository
這個接口之后, 會自動創(chuàng)建一個SimpleJpaRepository的實例.
public interface PersonRepository extends JpaRepository<Person,Long> {
}
下面的personRepository
其實就是個SimpleJpaRepository
的實例.
剩下的代碼就很簡單了, 增查改刪一氣呵成, 毫無廢話. 強(qiáng)烈建議讀者在IDE里面運(yùn)行完整版的代碼, 可以自己試著改一改, 玩一玩.
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();
就相當(dāng)于select * from person;
personRepository.save(account);
就能insert或者update表. 當(dāng)account里面的id == null
的時候是insert 相當(dāng)于執(zhí)行SQL語句insert into person ...
. 不為null, 比如 id == 1 的時候?qū)?yīng)update, 相當(dāng)于執(zhí)行update person ... where id=1
personRepository.delete(acct)
根據(jù)entity的id做刪除操作, 比如 acct.id等于1, 那么執(zhí)行就的是 delete from account where id=1;