Closed aol-nnov closed 7 years ago
Sorry you are experiencing issues. It might be that the UUID type is considered as being a managed type. I'll have a look later and let you know. If you could replace the id with a long or int in the meantime you should be able to test further.
@beikov Christian, thank you for a quick reply!
unfortunately, changing UUID to other type is not an option for me. some entities are created offline and later synchronised to the central db. Maintaining sequential keys is a nightmare in this scenario.
Btw, if it helps, here is how I'm using UUID:
@MappedSuperclass
@Data
public class Identifiable {
@Id
@GenericGenerator(name = "uuid-gen", strategy = "uuid2")
@GeneratedValue(generator = "uuid-gen")
@Type(type = "pg-uuid")
private UUID id;
}
and Hibernate reports, that type as registered.
[ main] org.hibernate.type.BasicTypeRegistry : HHH000270: Type registration [java.util.UUID] overrides previous : org.hibernate.type.UUIDBinaryType@7f9e8421
I just tried to reproduce your issue but I am unable to. Can you maybe post the library versions that you are using?
Christian, there is a big chance that I'm doing something wrong.. Seems, it was a mistake to plug it into existing project as I'm unable to provide you with minimal example now..
Anyway, library versions.. Spring boot 1.5.4.RELEASE with their dependency management plus
compile('com.blazebit:blaze-persistence-integration-spring-data:1.2.0-Alpha3')
compile('com.blazebit:blaze-persistence-integration-entity-view-spring:1.2.0-Alpha3')
compile('com.blazebit:blaze-persistence-jpa-criteria-api:1.2.0-Alpha3')
runtime('com.blazebit:blaze-persistence-integration-hibernate-5.2:1.2.0-Alpha3')
runtime('com.blazebit:blaze-persistence-jpa-criteria-impl:1.2.0-Alpha3')
I'm using postgresql server.
Since you seem to use Hibernate 5.0 could you please switch from blaze-persistence-integration-hibernate-5.2 to blaze-persistence-integration-hibernate-5 ?
Unfortunately, nothing changed after switching to
runtime('com.blazebit:blaze-persistence-integration-hibernate-5:1.2.0-Alpha3')
still the same stacktrace
So, I've nailed it, @beikov !
It's due to composite key in my entity, which is defined as follows:
@Data
@Entity
@IdClass(Availability.AvailabilityPK.class)
public class Availability implements Serializable {
private static final long serialVersionUID = -5432028594228229220L;
@Id
@ManyToOne(optional = false)
private Item item;
@Id
@ManyToOne(optional = false)
private Warehouse warehouse;
private BigDecimal amount;
@Data
@NoArgsConstructor
@EqualsAndHashCode
public static class AvailabilityPK implements Serializable {
private static final long serialVersionUID = 6423911231290364791L;
private UUID item;
private UUID warehouse;
}
}
Blaze-persistence config is a copy-paste from github readme:
@Configuration
public class BlazePersistenceConfig {
@PersistenceUnit
private EntityManagerFactory entityManagerFactory;
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Lazy(false)
public CriteriaBuilderFactory createCriteriaBuilderFactory() {
CriteriaBuilderConfiguration config = Criteria.getDefault();
// do some configuration
return config.createCriteriaBuilderFactory(entityManagerFactory);
}
@Bean
public EntityViewConfiguration entityViewConfiguration() {
return EntityViews.createDefaultConfiguration();
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Lazy(false)
// inject the criteria builder factory which will be used along with the entity view manager
public EntityViewManager createEntityViewManager(CriteriaBuilderFactory cbf, EntityViewConfiguration entityViewConfiguration) {
return entityViewConfiguration.createEntityViewManager(cbf);
}
}
If you would like, I can attach that minimal example, but anyway, everything you need to reproduce the issue is already in the ticket.
Could you try to add an @EmbeddedId
attribute instead of using the @IdClass
? Id classes are not very well tested and it seems to be because of the usage.
I think you would need to annotate the relations item and warehouse as being insertable = false and updatable = false in order to make this work.
Well, while moving from @IdClass
to @EmbeddedId
I've found a SO question.
Just removed @IdClass
and AvailabilityPK
all together, still having CONSTRAINT availability_pkey PRIMARY KEY (warehouse_id, item_id)
on a table and CriteriaBuilderFactory
instantiated just fine!
Let's see what I'll miss due to this move.
after those changes Availability
looks as follows:
@Data
@Entity
public class Availability implements Serializable {
private static final long serialVersionUID = -5432028594228229220L;
@Id
@ManyToOne(optional = false)
private Item item;
@Id
@ManyToOne(optional = false)
private Warehouse warehouse;
private BigDecimal amount;
}
I think you need an embedded id to benefit from the "single valued id access" optimizations that allows to avoid a join when filtering by FK columns. Imagine you have a class like
@Data
@Entity
public class Product implements Serializable {
@Id
@ManyToOne(optional = false)
private Availability availability;
}
If you do a query like FROM Product p WHERE p.availability.id = :id
the join to the availability table can be avoided when you have an embedded id like
@Data
@Embeddable
public class AvailabilityId implements Serializable {
private UUID item;
private UUID warehouse;
}
I don't think this is possible when using @IdClass
Christian, thank you for all the hints and quick resolution! But now I finally came to the real question why I've started looking into blaze persistence:
I need a sum(amount) on all warehouses for all items. I've started with AvailabilityView:
@EntityView(Availability.class)
public interface AvailabilityView {
Item getItem();
BigDecimal getAvailableTotal();
}
Could you help me with the query?
I can simply write it in plain sql, but how should I do it with CriteriaBuilderFactory
?
Thank you in advance!
Also, as it is mentioned in the comments of the SO question, you can't use EntityManager.find()
or EntityManager.getReference()
without an id class.
Of course I'll help you with the query :)
First of all, I'd like to recommend not using entity types like Item
in an entity view but to create an entity view for that too. Since you seem to care about the identity of the item, also don't forget to use @IdMapping
. The availableTotal
attribute needs a mapping. The context of the mapping is the entity type, so in mappings within AvailabilityView
you can access all entity attributes of the entity type Availability
.
@EntityView(Availability.class)
public interface AvailabilityView {
ItemView getItem();
@Mapping("SUM(amount)")
BigDecimal getAvailableTotal();
}
@EntityView(Item.class)
public interface ItemView {
@IdMapping
UUID getId();
// Other attributes
}
To fetch objects of that type, you need something like this
CriteriaBuilder<Availability> cb = criteriaBuilderFactory.create(entityManager, Availability.class);
CriteriaBuilder<AvailabilityView> viewCb = entityViewManager.applySettings(
EntityViewSetting.create(AvailabilityView.class),
cb
);
List<AvailabilityView> result = viewCb.getResultList();
This will create a JPQL query like
SELECT item.id, SUM(a.amount)
FROM Availability a
JOIN a.item item
GROUP BY item.id
THAT easy?? Oh, gosh! It's a simple repository findAll call! And paging support! Yay! Brilliant! :)) Thank you! You've made me a really quick start with your library!
The last question, if you don't mind :)
What if I need to order the result set by another entity? Say, I have
@Data
@Entity
class SortOrder {
Item item;
int placementWeight;
}
in normal sql I'd join it with Availability
and then ordered by placementWeight
.
How could I integrate it into your example?
Worth to mention, that SortOrder
does not strictly have records for all available Items
I suppose there is a relation in Item
for the SortOrder
? In that case you'd do something like this...
@EntityView(Availability.class)
public interface AvailabilityView {
ItemView getItem();
@Mapping("item.sortOrder.placementWeight")
Integer getPlacementWeight();
@Mapping("SUM(amount)")
BigDecimal getAvailableTotal();
}
@EntityView(Item.class)
public interface ItemView {
@IdMapping
UUID getId();
}
I added the placement weight as attribute so you can sort based on it. You could also put it into the ItemView
if you want.
To actually sort the results, you need to add a so called attribute sorter on the EntityViewSetting
CriteriaBuilder<Availability> cb = criteriaBuilderFactory.create(entityManager, Availability.class);
EntityViewSetting<AvailabilityView, CriteriaBuilder<AvailabilityView>> setting =
EntityViewSetting.create(AvailabilityView.class);
setting.addAttributeSorter("placementWeight", Sorters.ascending());
CriteriaBuilder<AvailabilityView> viewCb = entityViewManager.applySettings(setting, cb);
List<AvailabilityView> result = viewCb.getResultList();
I'm honestly not sure if pagination with the PaginatedCriteriaBuilder
API will work in this case as one of the current limitations is the inability to use group by. You might have to use setFirstResult
and setMaxResults
instead, but give it a try, I could be wrong 😆
No, Item
does not relate to SortOrder
. Only SortOrder
has a reference to Item
.
And I'd like not to link Item
with SortOrder
as it is a kind of helper entity for a single operation.
Since you use Hibernate 5 and not 5.1 or newer, you have to use a correlated subquery to do that, but PostgreSQL's optimizer should be able to optimize that into a join so no worries.
The mapping changes to
@EntityView(Availability.class)
public interface AvailabilityView {
ItemView getItem();
@MappingSubquery(PlacementWeightProvider.class)
Integer getPlacementWeight();
@Mapping("SUM(amount)")
BigDecimal getAvailableTotal();
class PlacementWeightProvider implement SubqueryProvider {
@Override
public <T> T createSubquery(SubqueryInitiator<T> subqueryBuilder) {
return subqueryBuilder.from(SortOrder.class, "sortOrder")
.where("sortOrder.item.id").eqExpression("OUTER(item.id)")
.select("sortOrder.placementWeight")
.end();
}
}
}
@EntityView(Item.class)
public interface ItemView {
@IdMapping
UUID getId();
}
The rest stays the same.
Astonishing! Seems like a Swiss army knife! Thank you so much!!
You probably think by yourself "ugly noobies.. Y don't read the docs!!1 Y???" no! We do! But reference manual is sooo immense and the desire to jump and try things is so strong, LOL!
Thank you for the help and for blaze-persistence! It's cool!
P.S.: will read the docs more, I promise! :-D
No problem, I'm here to help :) Don't hesitate to ask questions! After all, these questions lead to improvements, so please don't keep that to yourself 😆 May I ask how you came to know Blaze-Persistence?
Here is the issue for the IdClass support if you want to follow it: https://github.com/Blazebit/blaze-persistence/issues/399
Sure, will do!
I came to know about Blaze-Persistence when I was seeking for keyset pagination implementation for JPA. It was written in one article that there is not much choice in this area - blaze-persistence and one other implementation that I do not remember. ))
I'll post a link if I find it again!
Oh, here it is: http://use-the-index-luke.com/no-offset :)
Thanks :) I talked to Markus Winand(author of the article) a few weeks ago, and figured out, that the chosen keyset pagination strategy is not yet perfect, but that will change in the final 1.2.0 version: https://github.com/Blazebit/blaze-persistence/issues/419
Christian, everything is well so far, my code became much simpler with blaze-persistence :)
But now I'm trying to add a having
clause here
my naive approach looks like
viewCb.having("sum(amount)").gt(0);
but no luck - I'm getting java.lang.IllegalStateException: Having without group by
Please tell me what's wrong with it? Seems, I'm applying my having
clause before generated group by
is appended to the query or something lite that...
The Group By clause is added because of your use of an aggregate function. To be able to define a custom having clause you'd need to also specify a group by. Duplicates are filtered out so simply call groupBy("item.id")
before using having.
Sql group by
is correctly generated when I do not specify viewCb.having
.
It only vanishes away when I add my viewCb.having
call. Is it by design and I have to manually craft the whole group by
then?
Wow, just changed it to viewCb.groupBy("item.id").having("sum(amount)").gt(0);
and everything went fine!
I thought, viewCb.groupBy("item.id")
will overwrite the generated part of sql group by
statement, but I was wrong!
Thanks again for a quick help!
f you look into the core documentation there is something called implicit group by generation which is what is causing the group by being generated. This is a post step that just makes your life easier. If you want to have custom grouping or having predicates you can simply add them, on top of that, the rest of the needed group bys is added, skipping duplicates. It is by design that having is only allowed when specifying an explicit group by. Note that you don't have the specify the full group by, just the item Id is enough.
Hello!
Just stumbled upon your project and it looks quite interesting, however, I'm having a hard time wrapping my head around it. Please, help!
I use spring-data-jpa in my project and everything worked without a glitch before I've added blaze-persistence.. My entities have UUID PK and while constructing CriteriaBuilderFactory
I end up with exception:
Please advise how to overcome this issue!
Thanks in advance, Andrey