敲碎时间的人的个人专栏
上一篇

Hibernate映射计算的属性

广告
选中文字可对指定文章内容进行评论啦,→和←可快速切换按钮,绿色背景文字可以点击查看评论额。
大纲

在一般的实体映射中,一般都是一个属性对应数据库的某一列。今天我们来看看如何映射通过其他属性计算出来的属性。好了先看看具体的两个领域类:

新建一个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的语法,因此兼容性不是很好;因此这个方法需要酌情考虑。

 

版权声明:著作权归作者所有。

X

欢迎加群学习交流

联系我们