JPA Object Attribute Operation

The core of domain-driven design is domain object recognition, all operations are objects, which is also advocated by object-oriented programming.In the design of entity attributes, in addition to the standard data types recognized by the database, more and more complex object attributes are being considered.Let's turn our design perspective to the actual representation of the existence of objective things at the level of data storage.ORM framework also provides technical support for this, at least JPA is improving in this direction.

Code first: (This code is mainly designed to show functions, not necessarily the rationality of the specific design, due to slight deletion of the length code)

@Entity
public class SaleOrder implements Serializable {

    @Column(unique = true, updatable = false)
    private String orderCode;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "stationCode", column = @Column(name = "in_station_code")),
            @AttributeOverride(name = "storage.storageCode", column = @Column(name = "in_storage_code"))
    })
    private Station inStation;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "stationCode", column = @Column(name = "out_station_code")),
            @AttributeOverride(name = "storage.storageCode", column = @Column(name = "out_storage_code"))
    })
    private Station outStation;
    
    private LocalDateTime saleTime;

    private Address address;

    @ManyToOne(cascade = CascadeType.REFRESH)
    @JoinColumn(name = "memberCode", referencedColumnName = "memberCode")
    private Member member;

    @Enumerated(EnumType.STRING)
    private OrderState orderState;

    @OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
    @JoinColumn(name = "saleOrderCode", referencedColumnName = "orderCode")
    private List<OrderDetail> orderDetailList;
}

1. Use @OneToMany for associations

Members are reference objects and use memberCode to associate two tables. SaleOrder cannot have the privilege to manipulate the Members object, just do cascade refresh, and use CascadeType.REFRESH. When setting membership objects, only memberCode is sufficient here

saleOrder.setMember(Member.builder().memberCode("M001").build());

Of course, in actual business, the order and membership modules should be separate and belong to different micro-services, so you can use private String memberCode directly

2. Save enumerated values using @Enumerated

For code friendliness, most programs use enumerated objects to represent the state of an entity, while in database fields, we use int to represent the state value, but the readability is poor.In some cases where there are more states, string s can be used to enhance the readability of the data

@Enumerated(EnumType.STRING)
private OrderState orderState;

3. Use @Embeddable to merge subtable fields into the main table

The Address object is a member of SaleOrder and has always been part of it. Instead of maintaining tables separately at the database level, all fields are merged directly into the Sale_order table

@Embeddable
public class Address {
    private String userName;
    private String city;
    private String street;
}

** Someone will ask: ** Why not put the properties of the Address object directly in SaleOrder? **Answer: ** This is technically okay, but if the attributes userName,city,street in address are put into the main object, it will destroy the structural integrity of the saleOrder. There are no three attributes to represent the exact meaning of the address together and it will be more cumbersome to maintain.

4. Rewrite field names using @AttributeOverride

If a primary object references two identical child object attributes, in order to cause two child object attribute field renames, the field name of the attribute can be overridden using @AttributeOverride, or it can be applied to child objects of the child object

@Embedded
@AttributeOverrides({
        @AttributeOverride(name = "stationCode", column = @Column(name = "in_station_code")),
        @AttributeOverride(name = "storage.storageCode", column = @Column(name = "in_storage_code"))
})
private Station inStation;

...

@Embeddable
public class Station {
    @Column(updatable = false)
    private String stationCode;

    @Transient
    private String stationName;

    private Storage storage;
}

@Embeddable
public class Storage {
    private String storageCode;

    @Transient
    private String storageName;
}

5. Use @Convert for type conversion

We know that mysql's datetime type is not 100 seconds, but we need 100 seconds. The database can be saved as a timestamp Long type. To enhance readability programs that use LocalDateTime, we can use @Convert for type conversion. autoApply=true is an automatic conversion. When the types defined in the program are inconsistent with the types you want to store in the databaseType conversion is possible with @Convert


private LocalDateTime saleTime;
...

@Converter(autoApply = true)
public class LocalDateTimeConverter implements AttributeConverter<LocalDateTime, Long> {
    @Override
    public Long convertToDatabaseColumn(LocalDateTime localDateTime) {
        if (localDateTime == null){
            return null;
        }

        ZoneId zone = ZoneId.systemDefault();
        Instant instant = localDateTime.atZone(zone).toInstant();
        return instant.toEpochMilli();
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Long aLong) {
        if (null == aLong) {
            return null;
        }
        return LocalDateTime.ofInstant(Instant.ofEpochMilli(aLong), ZoneId.systemDefault());
    }
}

6. Use abstract class properties

public class OrderDetail {
    ...
    @Column(name = "product_code")
    @Convert(converter = ProductConverter.class)
    private AbstractProduct abstractProduct;
}

public abstract class AbstractProduct {
    private String productCode;
}

public class Book extends AbstractProduct{
    private String bookIsbn;
    private String bookName;
    private String author;
    private boolean isMagazine(){
        return true;
    }
}

public class Food extends AbstractProduct {
    private String foodCode;
    private String foodName;
    private Integer expiryDate;
    private boolean isExpires(){
        return false;
    }
}

An abstract class can be defined with @Convert for type conversion if the property object is ambiguous

public class ProductConverter implements AttributeConverter<AbstractProduct, String> {

    @Override
    public String convertToDatabaseColumn(AbstractProduct attribute) {
        if (attribute instanceof Food) {
            return ((Food) attribute).getFoodCode();
        } else if (attribute instanceof Book) {
            return ((Book) attribute).getBookIsbn();
        }
        return "";
    }


    @Override
    public AbstractProduct convertToEntityAttribute(String dbData) {
        if (dbData.startsWith("B")) {
            return Book.builder().bookIsbn(dbData).build();
        } else {
            return Food.builder().foodCode(dbData).build();
        }
    }
}

Defining attributes using abstract classes is primarily designed to maintain the diversity or extensibility of attribute classes, which may be a fixed value for the database itself, but for entity objects, programming can keep more possibilities of change.

Summary: This section mainly describes the entity attribute object settings relationship, the main purpose is to maintain the structure of the object clear and scalable, but also provides another design possibility.However, in the process of realizing business, it is necessary to adapt to local conditions and proceed from actual conditions to avoid over-design and increase complexity.

7. Source Code

https://gitee.com/hypier/barry-jpa/tree/master/jpa-section-3

Tags: Programming Attribute Database MySQL

Posted on Fri, 20 Mar 2020 00:06:42 -0400 by behrk2