前言
本篇內容主要是一些關於JPA的常用的一些用法等。內容也是很多是看其他博客學來的,順道在本系列博客里抽出一篇作為總結。下面讓我們來看看吧。
不過我更推薦大家讀本篇:https://lufficc.com/blog/spring-boot-jpa-basic
@Entity注解
標注在實體類上,每個實體類映射數據庫里的一張表,name屬性定義表名。默認表名為駝峰變量之間加下划線。例如:ProductInfo,生成的表名為product_info.
@Entity(name="userinfo") //default user_info public class UserInfo{ }
@Table注解
標注在實體類上,用於定義生成的表的相關屬性。name屬性定義表名。
@Entity @Table(name="userinfo") public class UserInfo{ }
@MappedSuperclass
標注在類上,但是不會映射到數據庫中的表。他的作用就是作為實體類通用的部分,比如id,status,create_time,update_time等通用字段。集成該類的實體生成的數據庫字段中包含帶有@MappedSuperclass注解的字段。
@MappedSuperclass public class DomainBase{ @Id @GenerateValue protected Long id; protected Long create_time; //getter setter }
@Entity(name="userinfo")
@AttributeOverride(name="create_time",column =@column(name="create_at"))
//@AttributeOverrides({...}) public class UserInfo extends DomainBase{ private String name; private Integer age; //getter setter }
生成的表(userinfo)中除了name 和 age 字段以外,還有DomainBase中的id和create_time字段。不過子類中還可以用@AttributeOverride屬性重新定義create_time 或者 id。
@Basic注解
這個注解不太常用,因為實體屬性默認注解就是@Basic。
@Entity public class UserInfo{ @Basic(optional=false,fetch=FetchType.LAZY) private String name; }
@Id注解
這個沒有什么好說的,就是主鍵。length定義長度。
@GeneratedValue注解
strategy:主鍵生成策略。可選值:
GenerationType.TABLE
GenerationType.SEQUENCE
GenerationType.IDENTITY
GenerationType.AUTO
默認是 GenerationType.AUTO。
generator:主鍵生成器名稱
@Entity(name="userinfo") public class UserInfo{ @Id @GenerateValue private Long id; }
@Transient注解
帶有該注解的字段不會再數據庫中創建。
@Temporal注解
日期類型的字段
@Entity(name = "user") public class User implements Serializable { @Id @GeneratedValue private Long id; @Temporal(TemporalType.DATE) private Date createDate; @Temporal(TemporalType.TIMESTAMP) private Date loginTime; @Temporal(TemporalType.TIME) private Date expireTime; // getters and setters }
@Column注解
這個表格 from http://fanlychie.github.io/post/jpa-column-annotation.html
參數 | 類型 | 描述 |
---|---|---|
name | String | 列的名稱,默認為屬性的名稱(Hibernate 映射列時,若遇到駝峰拼寫,會自動添加 _ 連接並將大寫字母改成小寫)。 |
unique | boolean | 列的值是否是唯一的。這是 @UniqueConstraint 注解的一個快捷方式, 實質上是在聲明唯一約束。默認值為 false。 |
nullable | boolean | 列的值是否允許為 null。默認為 true。 |
insertable | boolean | 列是否包含在 INSERT 語句中,默認為 true。 |
updatable | boolean | 列是否包含在 UPDATE 語句中,默認為 true。 |
columnDefinition | String | 生成列的 DDL 時使用的 SQL 片段。默認使用推斷的類型來生成 SQL 片段以創建此列。 |
table | String | 當前列所屬的表的名稱。 |
length | int | 列的長度,僅對字符串類型的列生效。默認為255。 |
precision | int | 列的精度,僅對十進制數值有效,表示有效數值的總位數。默認為0。 |
scale | int | 列的精度,僅對十進制數值有效,表示小數位的總位數。默認為0。 |
@OneToOne注解
一對一關聯,比如一個用戶只有一張身份證。
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; @OneToOne private IdCard idCard; }
@Entity public class IdCard { @Id @GeneratedValue private long id; private String address; private String province;
}
生成的SQL如下:
CREATE TABLE `id_card` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `address` varchar(255) DEFAULT NULL, `city` varchar(255) DEFAULT NULL, `province` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `id_card_id` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FK2y8xk8loi3isby5ju3eg7xan1` (`id_card_id`), CONSTRAINT `FK2y8xk8loi3isby5ju3eg7xan1` FOREIGN KEY (`id_card_id`) REFERENCES `id_card` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
可以看到,帶有@OneToOne注解的idcard字段生成了 id_card_id. 當然我們可以在加上 @JoinColumn(name = "card_id") 自定義關聯列名
@OneToMany注解
一對多關聯,比如一個用戶對應多個郵寄地址
@Entity public class Address { @Id private long id; private String detail; private long uid; }
@OneToMany @JoinColumn(name = "uid") private List<Address> addresses;
生成的SQL語句如下:
CREATE TABLE `address` ( `id` bigint(20) NOT NULL, `detail` varchar(255) DEFAULT NULL, `uid` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `FKbfn1l96xkepprn0hffrwr99dl` (`uid`), CONSTRAINT `FKbfn1l96xkepprn0hffrwr99dl` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ManyToOne注解
同理,多個郵寄地址對應一個用戶,Address類修改如下:
@Entity public class Address { @Id private long id; private String detail; @ManyToOne @JoinColumn(name="uid") private User user; }
@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; @OneToOne @JoinColumn(name = "card_id") private IdCard idCard; @OneToMany(mappedBy = "user") private List<Address> addresses; }
CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `card_id` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FK7nybup9cdqe2mh98vfnd035gd` (`card_id`), CONSTRAINT `FK7nybup9cdqe2mh98vfnd035gd` FOREIGN KEY (`card_id`) REFERENCES `id_card` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `address` ( `id` bigint(20) NOT NULL, `detail` varchar(255) DEFAULT NULL, `uid` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `FKbfn1l96xkepprn0hffrwr99dl` (`uid`), CONSTRAINT `FKbfn1l96xkepprn0hffrwr99dl` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@ManyToMany注解
多對多關聯。一個用戶對應多個標簽,每個標簽對應多個用戶。如果只是用戶單向關聯
@Entity public class Tag { @Id private long id; private String tagName; }
@ManyToMany private List<Tag> tags;
生成的SQL如下,會多余的創建一個新的關系表。
CREATE TABLE `tag` ( `id` bigint(20) NOT NULL, `tag_name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE `user_tags` ( `user_id` bigint(20) NOT NULL, `tags_id` bigint(20) NOT NULL, KEY `FKifh44dhy1ovc8a02b72ea5jd5` (`tags_id`), KEY `FKfcm4hc8oko2uqvf1bfypmp6st` (`user_id`), CONSTRAINT `FKfcm4hc8oko2uqvf1bfypmp6st` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`), CONSTRAINT `FKifh44dhy1ovc8a02b72ea5jd5` FOREIGN KEY (`tags_id`) REFERENCES `tag` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
雙向關聯,tag類修改如下:
@Entity public class Tag { @Id private long id; private String tagName; @ManyToMany(mappedBy = "tags") private List<User> users; }
此時生成的SQL語句和上文中的是一樣的。接下來可以加上 @JoinTable來自定義關聯列。User類修改如下:
@ManyToMany @JoinTable(name = "myUserTags",joinColumns = {@JoinColumn(name = "uid")},inverseJoinColumns = {@JoinColumn(name = "tid")}) private List<Tag> tags;
CREATE TABLE `my_user_tags` ( `uid` bigint(20) NOT NULL, `tid` bigint(20) NOT NULL, KEY `FKjq6w1do0t0ybthl9i65pg8h84` (`tid`), KEY `FKgceuixfh1q2eyjdkwyqxd1lr5` (`uid`), CONSTRAINT `FKgceuixfh1q2eyjdkwyqxd1lr5` FOREIGN KEY (`uid`) REFERENCES `user` (`id`), CONSTRAINT `FKjq6w1do0t0ybthl9i65pg8h84` FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Repository
查看定義如下:
public interface Repository<T, ID extends Serializable> { }
@JpaRepository
他繼承了PagingAndSortingRepository,PagingAndSortingRepository又繼承自CrudRepository
@NoRepositoryBean public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { /** * Returns all entities sorted by the given options. * * @param sort * @return all entities sorted by the given options */ Iterable<T> findAll(Sort sort); /** * Returns a {@link Page} of entities meeting the paging restriction provided in the {@code Pageable} object. * * @param pageable * @return a page of entities */ Page<T> findAll(Pageable pageable); }
@NoRepositoryBean public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> { /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#findAll() */ List<T> findAll(); /* * (non-Javadoc) * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort) */ List<T> findAll(Sort sort); /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable) */ List<T> findAll(Iterable<ID> ids); /* * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable) */ <S extends T> List<S> save(Iterable<S> entities); /** * Flushes all pending changes to the database. */ void flush(); /** * Saves an entity and flushes changes instantly. * * @param entity * @return the saved entity */ <S extends T> S saveAndFlush(S entity); /** * Deletes the given entities in a batch which means it will create a single {@link Query}. Assume that we will clear * the {@link javax.persistence.EntityManager} after the call. * * @param entities */ void deleteInBatch(Iterable<T> entities); /** * Deletes all entities in a batch call. */ void deleteAllInBatch(); /** * Returns a reference to the entity with the given identifier. * * @param id must not be {@literal null}. * @return a reference to the entity with the given identifier. * @see EntityManager#getReference(Class, Object) */ T getOne(ID id); /* (non-Javadoc) * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example) */ @Override <S extends T> List<S> findAll(Example<S> example); /* (non-Javadoc) * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort) */ @Override <S extends T> List<S> findAll(Example<S> example, Sort sort); }
@CrudRepository
查看定義如下,提供了基礎的CRUD方法
@NoRepositoryBean public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { /** * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the * entity instance completely. * * @param entity * @return the saved entity */ <S extends T> S save(S entity); /** * Saves all given entities. * * @param entities * @return the saved entities * @throws IllegalArgumentException in case the given entity is {@literal null}. */ <S extends T> Iterable<S> save(Iterable<S> entities); /** * Retrieves an entity by its id. * * @param id must not be {@literal null}. * @return the entity with the given id or {@literal null} if none found * @throws IllegalArgumentException if {@code id} is {@literal null} */ T findOne(ID id); /** * Returns whether an entity with the given id exists. * * @param id must not be {@literal null}. * @return true if an entity with the given id exists, {@literal false} otherwise * @throws IllegalArgumentException if {@code id} is {@literal null} */ boolean exists(ID id); /** * Returns all instances of the type. * * @return all entities */ Iterable<T> findAll(); /** * Returns all instances of the type with the given IDs. * * @param ids * @return */ Iterable<T> findAll(Iterable<ID> ids); /** * Returns the number of entities available. * * @return the number of entities */ long count(); /** * Deletes the entity with the given id. * * @param id must not be {@literal null}. * @throws IllegalArgumentException in case the given {@code id} is {@literal null} */ void delete(ID id); /** * Deletes a given entity. * * @param entity * @throws IllegalArgumentException in case the given entity is {@literal null}. */ void delete(T entity); /** * Deletes the given entities. * * @param entities * @throws IllegalArgumentException in case the given {@link Iterable} is {@literal null}. */ void delete(Iterable<? extends T> entities); /** * Deletes all entities managed by the repository. */ void deleteAll(); }
代碼重構
通過對JPA注解的學習,我突然發現在上一篇的講解中對好友關系的設計以及群組關系的設計不夠優雅,導致我用了笨的方法實現。重新整理后關系如下:
用戶 @OneToMany 好友分組(注:一個用戶可以創建多個分組,每個人組只屬於某個用戶)
用戶 @OneToMany 大群 (注:一個用戶可以創建多個群,每個群只屬於一個創建者)
用戶 @ManyToMany 好友分組(注:每個分組內可以有多個好友,每個用戶可以屬於不同好友的分組內)
用戶 @ManyToMany 大群(注:每個群可以有多個用戶成員,每個用戶可以屬於不同的群)
代碼如下:
@Entity public class User extends DomainBase{ private String avatar; private String userName; private String sign; //一對多,一個用戶可以創建多個好友分組 @OneToMany(mappedBy = "owner") private List<FriendGroup> friendGroupsOwner; //多對多,因為一個分組可以有多個用戶(好友) @ManyToMany @JoinTable(name = "user_friend_group",joinColumns = {@JoinColumn(name = "uid")},inverseJoinColumns = {@JoinColumn(name = "group_id")}) private List<FriendGroup> friendGroupsIn; @OneToMany(mappedBy = "owner") private List<BigGroup> bigGroupsOwner; @ManyToMany @JoinTable(name = "user_big_group",joinColumns = {@JoinColumn(name = "uid")},inverseJoinColumns = {@JoinColumn(name = "group_id")}) private List<BigGroup> bigGroupsIn; }
@Entity public class FriendGroup extends DomainBase { @Column(length = 20) private String name; @ManyToMany(mappedBy = "friendGroupsIn") private List<User> users; @ManyToOne @JoinColumn(name = "uid") private User owner; }
@Entity public class BigGroup extends DomainBase { @Column(length = 20) private String groupName; @Column(length = 120) private String avatar; @Column(length = 100) private String description; @ManyToMany(mappedBy = "bigGroupsIn") private List<User> users; @ManyToOne @JoinColumn(name = "uid") private User owner; }
重構后的代碼清爽多了,關系也顯而易見,去除了多余的類。service層的代碼也更簡潔。
/** * 獲取init接口所需要的數據結果 *@param userId 用戶ID *@return 返回 JsonResult(LayimInitDataViewModel) */ public JsonResult getBaseList(long userId){ LayimInitDataViewModel resultViewModel = new LayimInitDataViewModel(); //開始構造 //獲取用戶基本信息 User user = userRepository.findOne(userId); if(user == null){ return ResultUtil.fail(LAYIM_ENUM.NO_USER); } //映射用戶信息 UserViewModel mine = LayimMapper.INSTANCE.mapUser(user); resultViewModel.setMine(mine); //獲取好友分組信息 List<FriendGroup> friendGroups = user.getFriendGroups(); List<FriendGroupViewModel> friendGroupViewModels = new ArrayList<FriendGroupViewModel>(friendGroups.size()); //遍歷好友分組 for (FriendGroup friendGroup : friendGroups){ List<User> usersInGroup = friendGroup.getUsers(); //先映射群組信息 FriendGroupViewModel friendGroupViewModel = LayimMapper.INSTANCE.mapFriendGroup(friendGroup); //將每個組的人放到好友分組里面 friendGroupViewModel.setList(LayimMapper.INSTANCE.mapUser(usersInGroup)); friendGroupViewModels.add(friendGroupViewModel); } resultViewModel.setFriend(friendGroupViewModels); //獲取群組信息 List<BigGroup> bigGroups = user.getBigGroups(); resultViewModel.setGroup(LayimMapper.INSTANCE.mapBigGroup(bigGroups)); return ResultUtil.success(resultViewModel); }
運行程序,結果正常,偶也!
總結
對於JPA就介紹這么多吧,內容很淺。Repository里面還有很多有意思的東東,當然其他博客也有介紹的很多,並且比我寫的詳細,所以我就不在過多闡述了。不過后期都會用到,比如聊天記錄保存,分頁讀取等。增刪該查肯定是少不了的。
還有就是在學習的過程中也會對代碼理解有更深的層次,所以不要只看,一定要自己敲出來。
下篇預告:從零一起學Spring Boot之LayIM項目長成記(五)websocket