在一般的实体映射中,一般都是一个属性对应数据库的某一列。今天我们来看看如何映射通过其他属性计算出来的属性。好了先看看具体的两个领域类:
新建一个Account.java
@Entity(name = "Account")
@Table(name = "account")
public class Account {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private User owner;
private String iban;
private long cents;
private double interestRate;
private Timestamp createdOn;
@Transient
private double dollars;
@Transient
private long interestCents;
@Transient
private double interestDollars;
public Account() {
}
public Account(
Long id, User owner, String iban,
long cents, double interestRate, Timestamp createdOn) {
this.id = id;
this.owner = owner;
this.iban = iban;
this.cents = cents;
this.interestRate = interestRate;
this.createdOn = createdOn;
}
@PostLoad
private void postLoad() {
this.dollars = cents / 100D;
long months = createdOn.toLocalDateTime().until(
LocalDateTime.now(),
ChronoUnit.MONTHS)
;
double interestUnrounded = (
(interestRate / 100D) * cents * months
) / 12;
this.interestCents = BigDecimal.valueOf(interestUnrounded)
.setScale(0, BigDecimal.ROUND_HALF_EVEN)
.longValue();
this.interestDollars = interestCents / 100D;
}
public double getDollars() {
return this.dollars;
// return cents / 100D;
}
public long getInterestCents() {
return this.interestCents;
// long months = createdOn.toLocalDateTime().until(
// LocalDateTime.now(),
// ChronoUnit.MONTHS
// );
//
// double interestUnrounded = (
// (interestRate / 100D) * cents * months
// ) / 12;
//
// return BigDecimal.valueOf(interestUnrounded)
// .setScale(0, BigDecimal.ROUND_HALF_EVEN)
// .longValue();
}
public double getInterestDollars() {
return this.interestDollars;
// return getInterestCents() / 100D;
}
}
新建一个User.java
@Entity(name = "User")
@Table(name = "user")
public class User {
@Id
private Long id;
private String firstName;
private String lastName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
注意,这里我们在Account类中新增了三个属性,分别是:dollars,interestCents,interestDollars
,而这些属性通过@Preload这个监听器方法来初始化,并且通过相应的get方法将属性暴露出来。
下面看看测试方法:
public class FormulaPreloadTest extends AbstractTest {
@Override
protected Class[] entities() {
return new Class[]{
Account.class,
User.class
};
}
@Test
public void insertAccount() {
doInJPA(entityManager -> {
User user = new User();
user.setId(1L);
user.setFirstName("John");
user.setFirstName("Doe");
entityManager.persist(user);
Account account = new Account(
1L,
user,
"ABC123",
12345L,
6.7,
Timestamp.valueOf(
LocalDateTime.now().minusMonths(3)
)
);
entityManager.persist(account);
});
doInJPA(entityManager -> {
Account account = entityManager.find(Account.class, 1L);
assertEquals(123.45D, account.getDollars(), 0.001);
assertEquals(207L, account.getInterestCents());
assertEquals(2.07D, account.getInterestDollars(), 0.001);
});
}
}
运行之后可以看看,我们可以直接拿到这些属性的值,验证通过:
日志信息如下:
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:2, Time:0, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["drop table if exists account CASCADE "], Params:[]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:2, Time:0, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["drop table if exists user CASCADE "], Params:[]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:3, Time:0, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["create table account (id bigint not null, cents bigint not null, createdOn timestamp, iban varchar(255), interestRate double not null, owner_id bigint, primary key (id))"], Params:[]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:3, Time:1, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["create table user (id bigint not null, firstName varchar(255), lastName varchar(255), primary key (id))"], Params:[]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:3, Time:1, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["alter table account add constraint FKlijilgu3y8bx1rb3oirmqlw5k foreign key (owner_id) references user"], Params:[]
[INFO] 2023-01-04 13:34:16 method: org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator.initiateService(JtaPlatformInitiator.java:52)----HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:4, Time:0, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["insert into user (firstName, lastName, id) values (?, ?, ?)"], Params:[(Doe,NULL(VARCHAR),1)]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:4, Time:0, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["insert into account (cents, createdOn, iban, interestRate, owner_id, id) values (?, ?, ?, ?, ?, ?)"], Params:[(12345,2022-10-04 13:34:16.4853734,ABC123,6.7,1,1)]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:5, Time:0, Success:True, Type:Prepared, Batch:False, QuerySize:1, BatchSize:0, Query:["select account0_.id as id1_0_0_, account0_.cents as cents2_0_0_, account0_.createdOn as createdo3_0_0_, account0_.iban as iban4_0_0_, account0_.interestRate as interest5_0_0_, account0_.owner_id as owner_id6_0_0_ from account account0_ where account0_.id=?"], Params:[(1)]
[INFO] 2023-01-04 13:34:16 method: org.hibernate.tool.schema.internal.SchemaDropperImpl$DelayedDropActionImpl.perform(SchemaDropperImpl.java:538)----HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:6, Time:1, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["drop table if exists account CASCADE "], Params:[]
[INFO] 2023-01-04 13:34:16 method: net.ttddyy.dsproxy.support.CommonsLogUtils.writeLog(CommonsLogUtils.java:23)----Name:DATA_SOURCE_PROXY, Connection:6, Time:0, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["drop table if exists user CASCADE "], Params:[]
此外还有另一种方法,那就是hibernate提供的@Formula注解:
代码如下:
public class FormulaTest extends AbstractTest {
@Override
protected Class[] entities() {
return new Class[]{
Account.class,
User.class
};
}
@Test
public void insertAccount() {
doInJPA(entityManager -> {
User user = new User();
user.setId(1L);
user.setFirstName("John");
user.setFirstName("Doe");
entityManager.persist(user);
Account account = new Account(
1L,
user,
"ABC123",
12345L,
6.7,
Timestamp.valueOf(
LocalDateTime.now().minusMonths(3)
)
);
entityManager.persist(account);
});
doInJPA(entityManager -> {
Account account = entityManager.find(Account.class, 1L);
assertEquals(123.45D, account.getDollars(), 0.001);
assertEquals(207L, account.getInterestCents());
assertEquals(2.07D, account.getInterestDollars(), 0.001);
});
}
@Entity(name = "Account")
@Table(name = "account")
public static class Account {
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private User owner;
private String iban;
private long cents;
private double interestRate;
private Timestamp createdOn;
@Formula("cents::numeric / 100")
private double dollars;
@Formula("round((interestRate::numeric / 100) * cents * date_part('month', age(now(), createdOn))/ 12 ")
private long interestCents;
@Formula("round((interestRate::numeric / 100) * cents * date_part('month', age(now(), createdOn))/ 12)/ 100::numeric ")
private double interestDollars;
public Account() {
}
public Account(
Long id, User owner, String iban,
long cents, double interestRate, Timestamp createdOn) {
this.id = id;
this.owner = owner;
this.iban = iban;
this.cents = cents;
this.interestRate = interestRate;
this.createdOn = createdOn;
}
@Transient
public double getDollars() {
return this.dollars;
}
@Transient
public long getInterestCents() {
return this.interestCents;
}
@Transient
public double getInterestDollars() {
return this.interestDollars;
}
}
@Entity(name = "User")
@Table(name = "user")
public static class User {
@Id
private Long id;
private String firstName;
private String lastName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
}
注意,这里我们给属性添加的@Formula注解,并且将对应属性的get方法标注为@Transient
,这里有一个小小的问题是@Formula注解的内容依赖于具体的数据库SQL的语法,因此兼容性不是很好;因此这个方法需要酌情考虑。