從零一起學Spring Boot之LayIM專案長成記(四) Spring Boot JPA 深入瞭解

丶Pz發表於2017-11-06

前言

  本篇內容主要是一些關於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

相關文章