@Mapping(fetch = FetchStrategy.MULTISET) breaks boolean fields mapping.
@Mapping(fetch = FetchStrategy.MULTISET) breaks boolean fields mapping.
Description
My DripTriggerEntityView class boolean fields are always false If I use @Mapping(fetch = FetchStrategy.MULTISET). If I use @Mapping(fetch = FetchStrategy.JOIN) everything is ok, boolean fields have correct values.
My views and method to fetch view:
public Optional<DripCampaignEntityView> findOneById(Long id){
CriteriaBuilder<DripCampaignEntity> cb = cbFactory.create(em, DripCampaignEntity.class)
.where(DripCampaignEntity_.DELETED_AT).isNull()
.where(DripCampaignEntity_.ID).eq(id);
CriteriaBuilder<DripCampaignEntityView> postWithAuthorViewCriteriaBuilder =
viewManager.applySetting(EntityViewSetting.create(DripCampaignEntityView.class), cb);
return Optional.of(postWithAuthorViewCriteriaBuilder.getSingleResult());
}
@EntityView(DripCampaignEntity.class)
public interface DripCampaignEntityFlatView {
Date getCreatedAt();
Date getUpdatedAt();
Long getCreatedById();
Long getLastModifiedById();
Long getOwnerId();
@IdMapping
Long getId();
String getName();
Instant getPreferredSendDate();
Instant getPreferredSendTime();
boolean isEnabled();
Instant getLastSentAt();
int getStepsCount();
Instant getDeletedAt();
}
@EntityView(DripCampaignEntity.class)
public interface DripCampaignEntityView extends DripCampaignEntityFlatView {
@Mapping(fetch = FetchStrategy.MULTISET)
List<DripCampaignStepEntityView> getSteps();
@Mapping(fetch = FetchStrategy.MULTISET)
Set<DripTriggerEntityView> getCampaignSubscriptionTriggers();
}
@EntityView(DripTriggerEntity.class)
@EntityViewInheritance
public interface DripTriggerEntityView {
@IdMapping
Long getId();
Date getCreatedAt();
Date getUpdatedAt();
Long getCreatedById();
Long getLastModifiedById();
Long getOwnerId();
boolean isRetrospectionEnabled();
boolean isEnabled();
DripTriggerType getType();
}
It generates query:
select dripcampai0_.id as col_0_0_,
(select json_arrayagg(json_object('f0', cast(case
when driptrigge1_.type = 'USER_ADDED_TO_CONTACT_LIST' then 1
when driptrigge1_.type = 'USER_TAG_ASSIGNED' then 2
else 0 end as char), 'f1', cast(driptrigge1_.id as char), 'f2',
cast(driptrigge1_.created_at as char), 'f3',
cast(driptrigge1_.created_by as char), 'f4',
cast(driptrigge1_.enabled as char), 'f5',
cast(driptrigge1_.last_modified_by as char), 'f6',
cast(driptrigge1_.owner_id as char), 'f7',
cast(driptrigge1_.retrospection_enabled as char), 'f8',
cast(driptrigge1_.type as char), 'f9', cast(driptrigge1_.updated_at as char),
'f10',
(select json_arrayagg(json_object('f0', cast(crmcontact3_.id as char), 'f1',
cast(crmcontact3_.content_type as char),
'f2', cast(crmcontact3_.deleted_at as char),
'f3', cast(crmcontact3_.description as char),
'f4', cast(crmcontact3_.dynamic as char),
'f5', cast(crmcontact3_.global as char),
'f6',
cast(crmcontact3_.last_reculculation_at as char),
'f7', cast(crmcontact3_.name as char), 'f8',
cast(crmcontact3_.temporary as char)))
from drip_trigger_user_added_to_contact_list__contact_lists contactlis2_,
crm_contact_list crmcontact3_
where driptrigge1_.id = contactlis2_.trigger_id
and contactlis2_.contact_list_id = crmcontact3_.id
and driptrigge1_.type = 'USER_ADDED_TO_CONTACT_LIST'), 'f11',
(select json_arrayagg(json_object('f0', cast(entitytag5_.id as char), 'f1',
cast(entitytag5_.name as char), 'f2',
cast(entitytag5_.scope as char), 'f3',
cast(entitytag5_.shared as char)))
from drip_trigger_contact_tag_assigned_selected_tags tags4_,
tag entitytag5_
where driptrigge1_.id = tags4_.trigger_id
and tags4_.tag_id = entitytag5_.id
and driptrigge1_.type = 'USER_TAG_ASSIGNED')))
from drip_trigger driptrigge1_
left outer join drip_trigger_entity_user_tag_assigned_entity driptrigge1_1_
on driptrigge1_.id = driptrigge1_1_.id
left outer join drip_trigger_user_added_to_contact_list driptrigge1_2_
on driptrigge1_.id = driptrigge1_2_.id
where driptrigge1_.drip_campaign = dripcampai0_.id) as col_1_0_,
dripcampai0_.created_at as col_2_0_,
dripcampai0_.created_by as col_3_0_,
dripcampai0_.deleted_at as col_4_0_,
dripcampai0_.enabled as col_5_0_,
dripcampai0_.last_modified_by as col_6_0_,
dripcampai0_.last_sent_at as col_7_0_,
dripcampai0_.name as col_8_0_,
dripcampai0_.owner_id as col_9_0_,
dripcampai0_.preferred_send_date as col_10_0_,
dripcampai0_.preferred_send_time as col_11_0_,
(select json_arrayagg(json_object('f0', cast(dripcampai6_.id as char), 'f1',
cast(dripcampai6_.created_at as char), 'f2',
cast(dripcampai6_.created_by as char), 'f3',
cast(dripcampai6_.deleted_at as char), 'f4',
cast(dripcampai6_.interval_seconds as char), 'f5',
cast(dripcampai6_.last_modified_by as char), 'f6',
cast(dripcampai6_.owner_id as char), 'f7',
cast(dripcampai6_.reply_for as char), 'f8',
cast(dripcampai6_.send_from as char), 'f9',
cast(dripcampai6_.step_index as char), 'f10',
cast(dripcampai6_.subject as char), 'f11',
cast(dripcampai6_.template_id as char), 'f12', cast(dripcampai7_.body as char),
'f13', cast(dripcampai7_.created_at as char), 'f14',
cast(dripcampai7_.created_by as char), 'f15', cast(dripcampai7_.json as char),
'f16', cast(dripcampai7_.last_modified_by as char), 'f17',
cast(dripcampai7_.owner_id as char), 'f18',
cast(dripcampai7_.updated_at as char), 'f19',
cast(dripcampai6_.updated_at as char)))
from drip_campaign_step dripcampai6_
inner join drip_campaign_template dripcampai7_ on dripcampai6_.template_id = dripcampai7_.id
where dripcampai6_.drip_campaign_id = dripcampai0_.id) as col_12_0_,
dripcampai0_.steps_count as col_13_0_,
dripcampai0_.updated_at as col_14_0_
from drip_campaign dripcampai0_
where (dripcampai0_.deleted_at is null)
and (dripcampai0_.deleted_at is null)
and dripcampai0_.id = 29
This SQL produces Json for trigger:
[
{
"f0": "2",
"f1": "24",
"f2": "2023-11-17 17:25:15.633000",
"f3": "2062",
"f4": "1",
"f5": "2062",
"f6": "2062",
"f7": "1",
"f8": "USER_TAG_ASSIGNED",
"f9": "2023-11-17 18:02:03.137000",
"f10": null,
"f11": [
{
"f0": "96",
"f1": "tag2",
"f2": "USER",
"f3": "0"
}
]
}
]
as you can see both f4(enabled) and f7(retrospection_enabled) are true, but object contains only false...
Expected behavior
boolean fields have valid value
Actual behavior
boolean fields always false
Steps to reproduce
already provided
Environment
blaze-persistence 1.6.8 spring-boot-starter-parent 2.7.17 Windows 11 Database Mysql 8
DripCampaignEntity:
@Table(name = "drip_campaign")
@Entity(name = "DripCampaign" )
@Audited
@Getter
@Setter
@Builder(toBuilder = true)
@AllArgsConstructor
@NoArgsConstructor
@Where(clause="deleted_at is null")
@NamedEntityGraph(name = DripCampaignEntity.DRIP_CAMPAIGN_ENTITY_GRAPH_ALL,
attributeNodes = {
@NamedAttributeNode(value = "steps", subgraph = "steps.template"),
@NamedAttributeNode(value = "campaignSubscriptionTriggers"),
},subgraphs = {
@NamedSubgraph(name = "steps.template",
attributeNodes =
{@NamedAttributeNode("template")})
})
public class DripCampaignEntity extends AbstractAuditingResource<Long> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
private Long id;
@Size(max = 1024)
private String name;
private Instant preferredSendDate;
private Instant preferredSendTime;
@Column(nullable = false, columnDefinition = "boolean default false")
@Builder.Default
private boolean enabled = false;
@Column(updatable = false,insertable = false)
private Instant lastSentAt;
@Column(name="steps_count", nullable = false, columnDefinition = "int default 0")
private int stepsCount = 0;
@OneToMany(mappedBy = "dripCampaign", fetch = FetchType.LAZY)
@Where(clause="deleted_at is null")
@OrderBy("stepIndex")
@Builder.Default
@Fetch(FetchMode.SUBSELECT)
private List<DripCampaignStepEntity> steps = new ArrayList<>();
@NotAudited
@OneToMany(fetch = FetchType.LAZY,mappedBy = "dripCampaign",orphanRemoval = true,cascade={CascadeType.REMOVE})
private Set<DripTriggerEntity> campaignSubscriptionTriggers = new HashSet<>();
/*Don't use directly*/
@NotAudited
@OneToMany(mappedBy = "dripCampaign", fetch = FetchType.LAZY)
@Builder.Default
private Set<DripCampaignSubscriberEntity> subscribedUsers = new HashSet<>();
@Column(updatable = false)
private Instant deletedAt;
public final static String DRIP_CAMPAIGN_ENTITY_GRAPH_ALL = "drip_campaign.all";
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
DripCampaignEntity that = (DripCampaignEntity) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}
DripTriggerEntity:
@Table(name = "drip_trigger")
@Entity(name="DripTrigger")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type",discriminatorType=DiscriminatorType.STRING)
@Getter
@Setter
public abstract class DripTriggerEntity extends AbstractAuditingResource<Long> {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false)
protected Long id;
/*
* The variable determines whether the logic should be triggered for existing data (true)
* or only for new ones (false).
*/
@Column(nullable = false, columnDefinition = "boolean default false")
protected boolean retrospectionEnabled = false;
@Builder.Default
@Column(nullable = false, columnDefinition = "boolean default false")
protected boolean enabled = false;
@Enumerated(EnumType.STRING)
@Getter(AccessLevel.NONE)
@Column(name="type", insertable = false, updatable = false)
protected DripTriggerType type;
@ManyToOne(fetch = FetchType.LAZY,optional = false)
@JoinColumn(name = "drip_campaign", updatable = false)
protected DripCampaignEntity dripCampaign;
@Transient
public DripTriggerType getTriggerType() {
var discriminatorAnnotation = this.getClass().getAnnotation(DiscriminatorValue.class);
if(discriminatorAnnotation == null) {
throw new PublicCrmRuntimeException("Cannot get discriminator value of Base Entity class");
}
return DripTriggerType.valueOf(discriminatorAnnotation.value());
}
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
DripTriggerEntity that = (DripTriggerEntity) o;
return getId() != null && Objects.equals(getId(), that.getId());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}
Here is retreived object
MultisetTupleTransformer received "1"
I think that problem is here:
BooleanBasicUserType.fromString("1") returns false
As i understand converter now supports only "true", "false" values, and doesn't support 1 or 0, but it takes 1 or 0 if we use multiset.
Hey there. The fix should be pretty simple, so if you want to take a stab at trying to provide a PR for this, I'd be very grateful. In the meantime, you should be able to register a custom BooleanBasicUserType with a custom conversion strategy via com.blazebit.persistence.view.spi.EntityViewConfiguration#registerBasicUserType.