Persisting EnumSet in Postgresql with Hibernate - java

Effective Java, Item 32, states Use EnumSet instead of bit fields. I also found this nice tutorial on the topic. This book has been around for a while, so why don't I find any posts on how to persist an EnumSet with Hibernate? Well, I actually found this one, and another one, but they are both quite old, point to the same and much older solution, which unfortunately did not help me, perhaps because of my lack of deeper hibernate knowledge? Here is an abstract of my code:
public class MyThing {
public enum MyOptions {
FLAG1, FLAG2
}
#Id
private Long id;
#Column(name = "options")
private EnumSet<MyOptions> options;
// [other fields, getters, setters etc]
}
I've tried other annotations like
#ElementCollection
with and without (targetClass = MyOptions.class)
and
#JoinTable(name = "my_options",
joinColumns = #JoinColumn(name = "id"))
and also
#OneToMany
#JoinColumn(name = "options")
but with no luck.
Preferably, I'd store the information in a new column of the my_thing table, but I could also live with a separate table for the enums, if required.

Try this
#ElementCollection
#CollectionTable(name = "my_options",
joinColumns = #JoinColumn( name = "mything_id"))
#Column(name = "option")
#Enumerated(EnumType.STRING)
private Set<MyOptions> options;
With this configuration, you need a database table named my_options with columns option and mything_id which targets MyThing table.

Related

Duplicating elements in a ManyToMany relation

I have a ManyToMany relationship between Profile and ProfileExperience that is mapped as follows:
#ManyToMany
#JoinTable(name = "profile_experience_relations",
joinColumns = {
#JoinColumn(name = "profile_id")
},
inverseJoinColumns = {
#JoinColumn(name = "profile_experience_id")
})
private List<ProfileExperience> experiences;
I have added localization support inside of ProfileExperience, following this guide like so:
ProfileExperience Class
#OneToMany(mappedBy = "profileExperience", cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, orphanRemoval = true)
#MapKey(name = "localizedProfileExperiencePk.locale")
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
private Map<String, LocalizedProfileExperience> localizations = new HashMap<>();
LocalizedProfileExperience Class
#Entity
#Getter
#Setter
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class LocalizedProfileExperience {
#EmbeddedId
private LocalizedProfileExperiencePk localizedProfileExperiencePk;
#ManyToOne
#MapsId("id")
#JoinColumn(name = "profileExperienceId")
private ProfileExperience profileExperience;
private String value;
}
Composite PK Class
#Embeddable
#Getter
#Setter
public class LocalizedProfileExperiencePk implements Serializable {
private static final long serialVersionUID = 1L;
private String profileExperienceId;
private String locale;
public LocalizedProfileExperiencePk() {
}
Before adding the localization, there was no duplicate entries in the responses, however - everything retrieved is now duplicated.
I can solve the issue by using a Set, however I'm curious as to why this happened. What is the explanation? Can I solve it without using a set? Am I overlooking something incredibly simple?
The problem is that you are probably using join fetch or an entity graph to fetch nested collections. Now, when you look at the JDBC result set, you will see that there are many duplicate result set rows. If you have a profile with 2 profile experiences, and each has 3 localizations, you will see that you have 6 (2 * 3) duplicate rows. Theoretically, Hibernate could try to retain the expected object graph cardinality, but this is not so easy, especially when multiple collections are involved. Also, for certain collection mappings it would simply not be possible to do.
So the short answer to your problem is, never use a List unless duplicity matters to you. In this case, you will have an order column though, so even then it would be safe to use a list.
Implement the equal method of your data class. Hibernate need it.

JPA good practices mapping table

Im trying to apply the best practices to my JPA mapping table but i have a question about it, this is my table map:
#Entity
#Table(name = "TW_TABLE")
public class TwTable {
#Id
#Column(name = "N_ID")
private Long nId;
#Column(name = "N_IDCATALOGE")
private Long nIdCataloge;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "N_IDCATALOGE", insertable = false, updatable = false)
private TcCataloge tcCataloge;
}
this is my entity i have more columns and i have my getters and setters but i dont need them here, my questions is about the column N_IDCATALOGE, some querys only need the ID of the cataloge but some others will need the complete entity of tcCataloge, is it a good practice have both on the entity or should i delete the single column nIdCataloge and use the object to get the ID (on some cases i will only need the ID not the full object)?

List on object returned from database empty

I have three database tables that make up a many to many relationship. The tables are named Disposition, Disposition_Filter, and Dispositions_Disposition_Filter. Having mentioned that, Disposition_Disposition_Filter is the "join table".
The class DispositionFilterEntity looks like this:
#Getter
#Setter
#ToString
#EqualsAndHashCode
#NoArgsConstructor
#AllArgsConstructor
#Entity(name = "disposition_filter")
public class DispositionFilterEntity {
#GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(generator = "uuid2")
#Id
private String id;
#Column private String name;
}
While the DispositionEntity class looks like this:
#Getter
#Setter
#ToString
#EqualsAndHashCode
#Entity(name = "dispositions")
public class DispositionEntity {
#GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(generator = "uuid2")
#Id
private String id;
/** #see Disposition */
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String description;
#Column(nullable = false)
private String category;
#Column private boolean active;
#Column private String label;
#Column(name = "hidden_if_agent_not_assigned")
private Boolean hidden;
#Transient
public Disposition getAttributeTypeEnum() {
return Disposition.from(name);
}
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {#JoinColumn(name = "filter_id")},
inverseJoinColumns = {#JoinColumn(name = "disposition_id")})
private List<DispositionFilterEntity> filters;
}
When I run my code in the debugger and make a request to retrieve all the disposition filter objects from the database, I can see that the filters member on the disposition object is coming back empty despite the fact that data actually DOES exist in that relationship. If anyone knows why that list is coming back empty and could point me in the correct direction, I would very much appreciate it.
As you realized in your second update, your first problem have to do with the way you configured the dispositions relationship in DispositionFilterEntity, you interchanged the name of the columns in the joinColumns and inverseJoinColumns attributes. Instead of this:
#ManyToMany
#JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {#JoinColumn(name = "disposition_id")},
inverseJoinColumns = {#JoinColumn(name = "filter_id")})
private List<DispositionEntity> dispositions;
You need:
#ManyToMany
#JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {#JoinColumn(name = "filter_id")},
inverseJoinColumns = {#JoinColumn(name = "disposition_id")})
private List<DispositionEntity> dispositions;
I think the fetch type is not relevant for the problem although it is always advisable to fetch collections lazily.
Now, you are facing a stack overflow error.
The cause of this error is that you are resolving both relationships at the same time, in a circular fashion.
I mean, say for instance, that you are fetching a DispositionFilterEntity from the database.
In your code you are resolving the relationship with dispositions, either explicitly, by invoking getDispositions in your code, or implicitly, by other means - we will see later that this is the case.
For every DispositionEntity fetched, you are resolving the relationship with filters, again, either explicitly, by invoking getFilters in your code, or implicitly, by other means.
As indicated in the different comments, the first stack overflow you got was caused by the implementation of the toString, and equals and hashCode of your entities. It is always a good practice to not include any relationship field in the implementation of these methods in an entity to avoid lazy initialization or other problems like the one that you are facing
As far as you are using Lombok, in order to prevent the error you need to annotate the dispositions field in DispositionFilterEntity with #ToString.Exclude and with #EqualsAndHashCode.Exclude.
In addition, you can also annotate in the same way the filters field in DispositionEntity.
Once this problem was resolved you faced a new stack overflow error. This time the error was caused by the logic you are using to convert your entities to DTOs.
You are using Mapstruct for that purpose.
First, the provided stack trace - although the source code you provided later does not include all the methods initially indicated - shows methods related with the conversion of different entity-DTOs pairs. I think it is always better to use one Mapper for every entity-DTO pair.
Back to the stack overflow error, one option you have to avoid it is to ignore one of the fields, either dispositions or filters, in the corresponding mapping method by providing the corresponding #Mapping annotation: which of them, it will depend on your actual use case.
It is important to annotate the right mapper method, in this case, the one that maps every entity to the corresponding DTO, not the contrary.
I initially advised you to take care of JSON serialization an apply #JsonIgnore or whatever you need prevent the error but probably, as you already have a DTO, it will be no longer required.
Of course, if you do not need the relationship to be bidirectional, one possible solution is to remove one side of the relation. The reason why the filters field is not providing you any result is because you again interchanged the values of the joinColumns and inverseJoinColumns. Instead of this:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {#JoinColumn(name = "filter_id")},
inverseJoinColumns = {#JoinColumn(name = "disposition_id")})
private List<DispositionFilterEntity> filters;
You need:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "dispositions_disposition_filter",
joinColumns = {#JoinColumn(name = "disposition_id")},
inverseJoinColumns = {#JoinColumn(name = "filter_id")})
private List<DispositionFilterEntity> filters;
Please, always remember that the inverseJoinColumns references the column to join with the entity applicable in the other side of the collection.

OpenJPA - Nested OneToMany relationships merge issue

Posting this here as I wasn't seeing much interest here: http://www.java-forums.org/jpa/96175-openjpa-one-many-within-one-many-merge-problems.html
Trying to figure out if this is a problem with OpenJPA or something I may be doing wrong...
I'm facing a problem when trying to use OpenJPA to update an Entity that contains a One to Many relationship to another Entity, that has a One to Many relationship to another. Here's a quick example of what I'm talking about:
#Entity
#Table(name = "school")
public class School {
#Column(name = "id")
protected Long id;
#Column(name = "name")
protected String name;
#OneToMany(mappedBy = "school", orphanRemoval = true, cascade = CascadeType.ALL)
protected Collection<ClassRoom> classRooms;
}
#Entity
#Table(name = "classroom")
public class ClassRoom {
#Column(name = "id")
protected Long id;
#Column(name = "room_number")
protected String roomNumber;
#ManyToOne
#JoinColumn(name = "school_id")
protected School school;
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
protected Collection<Desk> desks;
}
#Entity
#Table(name = "desk")
public class Desk {
#Column(name = "id")
protected Long id;
#ManyToOne
#JoinColumn(name = "classroom_id")
protected ClassRoom classRoom;
}
In the SchoolService class, I have the following update method:
#Transactional
public void update(School school) {
em.merge(school);
}
I'm trying to remove a Class Room from the School. I remove it from the classRooms collection and call update. I'm noticing if the Class Room has no desks, there are no issues. But if the Class Room has desks, it throws a constraint error as it seems to try to delete the Class Room first, then the Desks. (There is a foreign key constraint for the classroom_id column)
Am I going about this the wrong way? Is there some setting I'm missing to get it to delete the interior "Desk" instances first before deleting the Class Room instance that was removed?
Any help would be appreciated. If you need any more info, please just let me know.
Thanks,
There are various bug reports around FK violations in OpenJPA when cascading remove operations to child entities:
The OpenJPA FAQ notes that the following:
http://openjpa.apache.org/faq.html#reorder
Can OpenJPA reorder SQL statements to satisfy database foreign key
constraints?
Yes. OpenJPA can reorder and/or batch the SQL statements using
different configurable strategies. The default strategy is capable of
reordering the SQL statements to satisfy foreign key constraints.
However ,you must tell OpenJPA to read the existing foreign key
information from the database schema:
It would seem you can force the correct ordering of the statements by either setting the following property in your OpenJPA config
<property name="openjpa.jdbc.SchemaFactory"> value="native(ForeignKeys=true)"/>
or by adding the org.apache.openjpa.persistence.jdbc.ForeignKey annotation to the mapping:
#OneToMany(mappedBy = "classRoom", orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#org.apache.openjpa.persistence.jdbc.ForeignKey
protected Collection<Desk> desks;
See also:
https://issues.apache.org/jira/browse/OPENJPA-1936

fetch data in ManyToOne relation using Restriction

There are two tables with #OneToMany and #ManyToOne bidirectional relation, like this:
#Entity
public class Asset {
private int id;
private int count;
#OneToMany
private Set<Dealing> dealings;
...
}
#Entity
public class Dealing {
private int id;
...
#ManyToOne
#JoinColumn(name = "customer_id", nullable = false, updatable = false)
private Customer customer;
#ManyToOne
#JoinColumn(name = "product_id", nullable = false, updatable = false)
private Product product;
#ManyToOne(cascade = CascadeType.ALL)
private Asset asset;
}
all things sound OK, but when I want to search data using Restriction like this,
session.createCriteria(Asset.class).add(Restrictions.eq("dealings.customer.id", customerId)).add(Restrictions.eq("dealing.product.id", productId)).list();
In this level I get this error,
could not resolve property: dealings.customer of: com.project.foo.model.Asset
one of the solutions are to change my strategy but i wasted time to find this,btw I don't have any idea about it, do you ?
First of all, you don't have a bidirectional OneToMany association, but two unrelated unidirectional associations. In a bidirectional OneToMany association the One side must be marked as the inverse of the Many side using the mappedBy attribute:
#OneToMany(mappedBy = "asset")
private Set<Dealing> dealings;
Second, using the criteria API for such static queries is overkill, and leads to code that is harder to read than necessary.I would simply use HQL which is much easier to read. Criteria should be used for dynamic queries, IMHO, but not for static ones:
select asset from Asset asset
inner join asset.dealings dealing
where dealing.customer.id = :customerId
and dealing.product.id = :productId
Whether you use HQL or Criteria, you can't use asset.dealings.customer, since asset.dealings is a collection. A collection doesn't have a customer attribute. To be able to reference properties from the Dealing entity, you need a join, as shown in the above HQL query. And it's the same for Criteria:
Criteria criteria = session.createCriteria(Asset.class, "asset");
criteria.createAlias("asset.dealings", "dealing"); // that's an inner join
criteria.add(Restrictions.eq("dealing.customer.id", customerId);
criteria.add(Restrictions.eq("dealing.product.id", productId);

Categories

Resources