spring-data-jpa 使用@Convert类保存实体时Hibernate的奇怪行为

kt06eoxx  于 2022-11-10  发布在  Spring
关注(0)|答案(1)|浏览(474)

因此,我有一个促进领域的类,如实体,服务,控制器等。
但是我在Entity类中有一个接口属性,根据POST时传递的参数,该示例将是保存在DB中的一个子类。但是我遇到了Hibernate在这方面的一个奇怪行为。如果我将该属性设置为promotionSeason = "easterPromotion"-首先,在控制台中,似乎被创建为ChristmasPromotionSeason,然后更新为EasterPromotionSeason,我不知道为什么,如果我再举一个例子,比如说:promotionSeason = "noPromotion",同样的问题...
我在查询时得到了错误的结果,以查看我们有什么促销基于促销季节,如果我有复活节促销保存在数据库中,将返回圣诞节促销在查询结果中,为什么会发生这个问题?你可以看到下面的Hibernate控制台日志...
促销实体:

@Entity
@org.hibernate.annotations.DynamicInsert
@org.hibernate.annotations.DynamicUpdate
@Access(AccessType.FIELD)
public class Promotion {

    @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) //pre Insert values
    private Long promotionId;

    //Strategy Pattern, maybe State pattern was more suitable?
    //check this -> https://stackoverflow.com/questions/51138344/hibernate-persisting-a-composition-interface-of-strategy-pattern
    @Convert(converter = PromotionConverter.class)
    @Column(name = "PROMOTION_SEASON", nullable = false)
    private PromotionSeason promotionSeason;

    public Promotion() {}

    public Promotion(PromotionSeason promotionSeason)
    {
        this.promotionSeason = promotionSeason;
    }
    public Long getPromotionId() {
        return promotionId;
    }

    public PromotionSeason getPromotionSeason() {
        return promotionSeason;
    }

    public void setPromotionSeason(PromotionSeason promotionSeason) {
        this.promotionSeason = promotionSeason;
    }
}

推广服务

@Service
public class PromotionService {

    private final PromotionRepository promotionRepository;
    private final PromotionCallingOthers promotionCallingOthers;

    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    public PromotionService(PromotionRepository promotionRepository, PromotionCallingOthers promotionCallingOthers) {
        this.promotionRepository = promotionRepository;
        this.promotionCallingOthers = promotionCallingOthers;
    }

    public void createPromotion(Promotion promotion)
    {

        System.out.println(promotion.getPromotionSeason().isSeason());
        this.promotionRepository.save(promotion);
    }

    public void addProducts(Promotion promotion, String productName) {
        Promotion createdPromotion = new Promotion(promotion.getPromotionSeason());

        ResponseEntity<Product> productResponseEntity = promotionCallingOthers.callProduct(productName);
        Session session = entityManager.unwrap(Session.class);
        session.update(productResponseEntity.getBody()); // for detached entity error

        createdPromotion.addProduct(productResponseEntity.getBody());

        double price = createdPromotion.getProductList().get(0).getProductPrice();
        double discountedPrice = createdPromotion.getPromotionSeason().applySeasonPromotionDiscount(price);

        double priceTo = getDigitsFormat(price - discountedPrice);
        Objects.requireNonNull(productResponseEntity.getBody()).setProductPrice(priceTo);

        createdPromotion.setNumberOfProductsAtPromotion(productResponseEntity.getBody().getProductQuantity());
        this.promotionRepository.save(createdPromotion);

    }

    private double getDigitsFormat(double numberToFormat)
    {
        DecimalFormat formatDecimal = new DecimalFormat("#.##");
        return Double.parseDouble(formatDecimal.format(numberToFormat));
    }

    public Promotion createPromotionWithType(String promotionType) {
        Promotion promotion = new Promotion();
        promotion.setPromotionSeason(setPromotionSeasonImplBasedOnType(promotionType));
        promotionRepository.save(promotion);
        return promotion;
    }

    public Promotion getPromotionSeasonBasedOnSomething(String promotionType)
    {
        PromotionSeason promotionSeason = setPromotionSeasonImplBasedOnType(promotionType);
        Promotion promotion = promotionRepository.findPromotionByPromotionSeason(promotionSeason);
        System.out.println(promotion.getPromotionSeason());
        return promotion;
    }

    private PromotionSeason setPromotionSeasonImplBasedOnType(String promotionType)
    {
        // eh, state pattern would be better i guess
        switch (promotionType.toLowerCase()) {
            case "christmas":
                return new PromotionChristmasSeason();
            case "easter":
                return new PromotionEasterSeason();
            default:
                return new NoPromotionForYouThisTimeMUHAHA();
        }
    }

    public Promotion test(String testam) {
        PromotionSeason promotionSeason = checkPromotionSeason(testam);

        System.out.println(promotionSeason.isSeason());

        Promotion promotion = promotionRepository.findWhatPromotionSeasonWeHave(promotionSeason);

        if (promotion == null) {

            System.out.println("promotion season ii in if: " + promotionSeason.isSeason());
            Promotion promotion1 = new Promotion(promotionSeason);
            System.out.println(promotion1.getPromotionSeason().isSeason());
            //?
            promotion = promotion1;
            System.out.println(promotion.getPromotionSeason().isSeason());

            promotion.setPromotionStore(promotion1.getPromotionStore());

            promotionRepository.save(promotion);
            System.out.println(promotion.getPromotionSeason().isSeason());
            return promotion;
        }

        promotion.setPromotionSeason(promotionSeason); 
        promotionRepository.save(promotion);
        System.out.println("promotion is" + promotion.getPromotionSeason().isSeason());
        return promotion;
    }

    private PromotionSeason checkPromotionSeason(String promotionSeason)
    {
        System.out.println("is \n" + promotionSeason.toLowerCase());
        switch (promotionSeason.toLowerCase().trim())
        {
            case "easter" :
                return new PromotionEasterSeason();
            case "christmas" :
                return new PromotionChristmasSeason();

            default:
                return new NoPromotionForYouThisTimeMUHAHA();
        }
    }

促销存储库:

@Repository
public interface PromotionRepository extends JpaRepository<Promotion, Long> {

    @Query("SELECT s FROM Promotion s WHERE s.promotionSeason = :promotionSeason")
    Promotion findWhatPromotionSeasonWeHave(@Param("promotionSeason") PromotionSeason promotionSeason);
    Promotion findPromotionByPromotionSeason(PromotionSeason promotionSeason);
}

促销控制器:

@RestController
@RequestMapping(value = "/promotions")
public class PromotionController {

    private final PromotionService promotionService;

    @Autowired
    public PromotionController(PromotionService promotionService) {
        this.promotionService = promotionService;
    }

    @PostMapping(value = "/createPromotion")
    public ResponseEntity<String> createPromotion(@RequestBody Promotion promotion)
    {
        promotionService.createPromotion(promotion);
        return ResponseEntity.status(HttpStatus.CREATED)
                .body("Done");
    }

    @GetMapping(value = "/createPromotion/{promotionType}")
    public ResponseEntity<Promotion> createPromotionType(@PathVariable String promotionType)
    {

        return ResponseEntity.status(HttpStatus.CREATED).body(promotionService.createPromotionWithType(promotionType));
    }

    //WRONG RESULT
    @GetMapping(value = "/getPromotion/{promotionType}")
    public ResponseEntity<Promotion> getPromotionType(@PathVariable String promotionType)
    {
        return ResponseEntity.status(HttpStatus.FOUND).body(promotionService.getPromotionSeasonBasedOnSomething(promotionType));
    }

    //WRONG RESULT
    @GetMapping(value = "/test/{promotion}")
    public Promotion check(@PathVariable String promotion)
    {
        return promotionService.test(promotion);
    }
}

促销季界面:

//see: -> https://www.youtube.com/watch?v=IlLC3Yetil0
//see: -> https://stackoverflow.com/questions/72155637/a-way-of-polymorphic-http-requests-using-postman/72158992#72158992

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "promotion")
@JsonSubTypes(
        {       @JsonSubTypes.Type(value = PromotionEasterSeason.class, name = "easterPromotion"),
                @JsonSubTypes.Type(value = PromotionChristmasSeason.class, name = "christmasPromotion"),
                @JsonSubTypes.Type(value = NoPromotionForYouThisTimeMUHAHA.class, name = "noPromotion")
        })
public interface PromotionSeason {

    String isSeason();

    double applySeasonPromotionDiscount(double initialPrice);
}

PromotionEasterSeason类-接口的实现:

@JsonTypeName(value = "easterPromotion")
public class PromotionEasterSeason implements PromotionSeason{

    private double promotionProcentToDiscount = 10.99f;

    @Override
    public String isSeason() {
        return "Is Easter Season Discount Time of the Year again!";
    }

    @Override
    public double applySeasonPromotionDiscount(double initialPrice) {
        System.out.println("Now you have to pay less with: " + calculateDiscount(initialPrice) + ", instead of: " + initialPrice);
        return calculateDiscount(initialPrice);
    }

    private double calculateDiscount(double initialPriceToDiscount)
    {
        return this.promotionProcentToDiscount  / initialPriceToDiscount;
    }
}

促销转换器类:

public class PromotionConverter implements AttributeConverter<PromotionSeason, String> {

    @Override
    public String convertToDatabaseColumn(PromotionSeason attribute) {
        return attribute.getClass().getSimpleName().trim().toLowerCase(Locale.ROOT);
    }

    @Override
    public PromotionSeason convertToEntityAttribute(@NotBlank String dbData) {
        return stateOfPromotion(dbData);
    }

    private PromotionSeason stateOfPromotion(String state)
    {
        return state.equals("easterPromotion") ? new PromotionEasterSeason() : new PromotionChristmasSeason();
    }
}

Hibernate SQL控制台:

2022-08-07 12:16:37.123 DEBUG 7432 --- [nio-8080-exec-2] org.hibernate.SQL                        : call next value for hibernate_sequence
2022-08-07 12:16:37.249 DEBUG 7432 --- [nio-8080-exec-2] org.hibernate.SQL                        : insert into PROJECT_HIBERNATE_Promotion (NUMBER_PRODUCTS_AT_PROMOTION, PROMOTION_SEASON, promotionId) values (?, ?, ?)
2022-08-07 12:16:37.258 TRACE 7432 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [INTEGER] - [0]
2022-08-07 12:16:37.260 DEBUG 7432 --- [nio-8080-exec-2] tributeConverterSqlTypeDescriptorAdapter : Converted value on binding : com.shoppingprojectwithhibernate.PromotionsModule.Domain.PromotionChristmasSeason@54de6f66 -> promotionchristmasseason
2022-08-07 12:16:37.260 TRACE 7432 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [promotionchristmasseason]
2022-08-07 12:16:37.261 TRACE 7432 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [1]
2022-08-07 12:16:37.271 DEBUG 7432 --- [nio-8080-exec-2] org.hibernate.SQL                        : update PROJECT_HIBERNATE_Promotion set PROMOTION_SEASON=? where promotionId=?
2022-08-07 12:16:37.273 DEBUG 7432 --- [nio-8080-exec-2] tributeConverterSqlTypeDescriptorAdapter : Converted value on binding : com.shoppingprojectwithhibernate.PromotionsModule.Domain.PromotionEasterSeason@18c401ef -> promotioneasterseason
2022-08-07 12:16:37.274 TRACE 7432 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [promotioneasterseason]
2022-08-07 12:16:37.275 TRACE 7432 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
6rvt4ljy

6rvt4ljy1#

PromotionConverter中的方法执行的操作不一致:
convertToDatabaseColumn将转换为促销季节类的小写名称,因此它将返回:promotioneasterseasonpromotionchristmasseasonnopromotionforyouthistimemuhaha。这是将存储在数据库中的字符串。
但是,convertToEntityAttribute方法(应该执行相反的操作)执行了其他操作:如果来自数据库的字符串是easterPromotion,则它将返回类型为PromotionEasterSeason的对象,否则返回类型为PromotionChristmasSeason的对象。
因此,当您在数据库中保存复活节促销时,会发生什麽情况:Hibernate将存储字符串promotioneasterseason。当您从数据库中读取该记录时,转换器注意到该记录与easterPromotion不匹配,因此它将返回PromotionChristmasSeason
解决方案:确保方法convertToDatabaseColumnconvertToEntityAttribute执行相反的操作。

相关问题