I have a project to access a database via REST, in which an old database has to be addressed in a modern way using javax.persistence annotations and JPA.
One of my classes fields look like this:
#Column(name = "properties_id", nullable = false)
private int propertiesId;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "properties_id", referencedColumnName = "dbid", insertable = false, updatable = false)
private PropertyList propertyList;
#Id
#Column(name = "propertiesIdx", nullable = false)
private int propertiesIdx;
As you can see in the fields, the column properties_id is used as part of the primary key and as a foreign key. Unfortunately, there is no possibility to change the database layout.
In the application, I need both fields: one for joining the parent-table and another for addressing the property directly.
But in the way it is currently implemented, it doesn't work.
Does somebody have an idea how to address this problem?
For further details of the problem please feel free to visit the git-repo:
https://github.com/SerNet/verinice-rest-service/tree/import
Related
I always use the following method to implement ManyToOne in class :
#Column(name = "buyer_id")
private Long buyerId;
#ManyToOne
#JoinColumn(name = "buyer_id", insertable = false, updatable = false)
UserGroup buyer;
However, I have a question whether it is better to use the following code:
#ManyToOne
#JoinColumn(name = "buyer_id", insertable = false, updatable = false)
UserGroup buyer;
or not?
In the first case, I always set the id value obtained in buyerId for saving after saving the UserGroup, but in the second case, I put the userGroup model completely (after save) in my entity then save entity.
I'm not sure if the method I use is the best.
I searched the internet but could not find a technical reason that the latter is not appropriate.
I have following classes in bidirectional many to many relationship.
#Table(name = "message")
#Entity
public class Message {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "message_id", unique = true, nullable = false)
private int id;
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
#JoinTable(name = "tags_messages",
joinColumns = #JoinColumn(name = "message_id", referencedColumnName = "message_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id", referencedColumnName = "tag_id"))
private Set<Tag> tags=new HashSet<>();
and
#Table
#Entity(name = "tag")
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tag_id", unique = true, nullable = false)
private int id;
#Column(name = "name", unique = false, nullable = false)
private String name;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.MERGE)
private Set<Message> messages;
When trying to save new Message, I got exception saying: "detached entity to persist...Tag". I got it to work by setting CascadeType.MERGE, but I don't understand why it is working. It would be great if someone can explain me why :)
Steps I did which lead to exception:
In db I already had two Tags objects and no Messages. I also had connecting empty table messages_tags
On frontend (Android) I create new Message object (without id), add one Tag (entire object, pulled from db, with id) to Message.
Send new Message to backend (using Retrofit). Hit my controller function, then service function in which I tried to save new Message with accompanying child Tags. At first, my cascading type annotation on both side, was like this:
#ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
I thought, since I have one part of relationship covered, that I need to cover other one as well. So I did this:
newMessage.getTags().forEach(t -> t.getMessages().add(newMessage));
messageRepository.save(newMessage) //Bum! exception
I commented out that line for setting other part of relationship, set MERGE as cascading type and save simply worked. WHY? Are there any other consequences I may experience while doing other CRUD operations on any of these entities?
When you add a new Tag to the Message on the frontend, you have a different persistent context from the one used on backend. That's why the tag entity is seen as detached (it has a PK but it is not in the backend's persistent context). Since you did not specify a cascade type for JPA to know that to do with Tag instance, the persist of the Message instance fails.
Once you specify the cascade type MERGE, the Tag instance is merged into the backend's persistent context and the save succeeds.
You can avoid the using of MERGE cascade by saving first the Tag instance.
I'm currently having an issue with a BUILD FAILED error when deploying my Netbeans project to Glassfish server. I get the following error from the log:
Exception Description: The #JoinColumns on the annotated element [field instrument] from the entity class [class entity.InstrumentExtRef] is incomplete. When the source entity class uses a composite primary key, a #JoinColumn must be specified for each join column using the #JoinColumns. Both the name and the referencedColumnName elements must be specified in each such #JoinColumn.. Please see server.log for more details.
The table that's having the issue is instrument_ext_ref and has a composite primary key of the following fields: instrument_id and instrument_code_type.
The entity class that seems to have the problem - InstrumentExtRef - has the following declarations and annotations.
#EmbeddedId
protected InstrumentExtRefPK instrumentExtRefPK;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 16)
#Column(name = "reference")
private String reference;
#JoinColumn(name = "instrument_code_type", referencedColumnName = "code_type", insertable = false, updatable = false)
#ManyToOne(optional = false)
private InstrumentCodeType instrumentCodeType1;
#JoinColumn(name = "instrument_id", referencedColumnName = "id", insertable = false, updatable = false)
#ManyToOne(optional = false)
private Instrument instrument;
And the entity class containing the #EmbeddedId details (InstrumentExtRefPK) looks like this:
#Embeddable
public class InstrumentExtRefPK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "instrument_id")
private int instrumentId;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 10)
#Column(name = "instrument_code_type")
private String instrumentCodeType;
Class InstrumentCodeType refers to table instrument_code_type which has a primary key code_type (the referenced column). Class Instrument refers to table instrument which itself has a composite primary key consisting of columns "id" (the referenced column) and column "exchange_exchange_code".
It seems obvious from the error message that the composite primary key in the InstrumentExtRef entity is the issue but based on my research of other similar (but not quite the same) issues I think my entity classes look OK. But I'm relatively new to this and may be missing something.
After doing some more research on creating/uses of composite keys in entity classes I saw a subtle point in the error message I was receiving.
"...must be specified for each join column using the #JoinColumn*s*". I wasn't using the #JoinColumns annotation. Or at least Netbeans didn't create the entity class that way. Probably because each join column is from a different source entity/table.
After correcting my entity classes as follows the project was able to be deployed successfully.
#JoinColumns({
#JoinColumn(name = "instrument_code_type", referencedColumnName = "code_type", insertable = false, updatable = false),
#JoinColumn(name = "instrument_id", referencedColumnName = "id", insertable = false, updatable = false)})
private InstrumentCodeType instrumentCodeType1;
private Instrument instrument;
It was here that I started to see what the problem might be:
https://access.redhat.com/site/documentation/en-US/JBoss_Enterprise_Application_Platform/5/html/Hibernate_Annotations_Reference_Guide/ch02s02s06.html
I suspect that my next problem will be something to do with the fact that the composite primary key is made up of columns from two different entities - instrument and instrumentCodeType. But for now the above change got me past the build problem and if that next problem arises I'll deal with it separately.
everyone.
I am have Customer and Service tables in one to many relation - one customer can have no or many services. The Service table has a customer_id column which is a foreign key referencing the primary key of the Customer table.
When retrieving customers from the database I need to get only the IDs of the related services and that is why I decided to use the #ElementCollection annotation.
My mapping for the Customer is as follows:
#Entity
#Table(name = "customer")
public class CustomerEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(nullable = false)
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "service", joinColumns = #JoinColumn(name = "customer_id", updatable = false, insertable = true))
#Column(name = "id", updatable = false, insertable = true)
private Set<Integer> serviceIds;
}
Everything works perfect when I get data from the database. The problem is when I try to update a customer in the database. Upon update Hibernate deletes all the Service table rows which reference the updated customer if the serviceIds member of the customer entity was set to null or an empty Set. I would like to avoid this behavior. I would like this serviceIds member to be read only for Hibernate and to be ignored when the customer is updated in the database - i.e. I want to update only the customer table and nothing else. Is it possible to achieve this using ElementCollection?
By the way I tried the following mapping and the update of the Customer does not lead to any deletions in the Service table even if I set the serviceIds to null or an empty Set.
#OneToMany
#JoinColumn(name = "customer_id", referencedColumnName = "id", updatable = false, insertable = false)
private Set<ServiceEntity> serviceIds;
Thank You for Your help.
When modifying - even - a single element in an #ElementCollection, Hibernate will delete the old collection (the one persisted in DB) and then insert the new image of the collection. The question was already asked here:
Hibernate - #ElementCollection - Strange delete/insert behavior
In order to avoid this problem you can:
Use a List of Integer with the #OrderColumn annotation as described in the above link.
Create an Entity class wrapping your Integer (+ #Generated #Id).
Best regards
I am hitting an issue with my Hibernate backed Jpa queries returning data that is not up to date. I assume it is an issue with pulling data from the cache instead of the database itself.
For example, I will change and persist an object on one page and then go back to the previous page, which lists rows of the database, and it will show the objects as they existed prior to the change. I can see my query fire from my DAO in my logs and I can go into the database and see that the changes have been persisted, but JPA is not pulling up to date data from the database when I move on to the next page.
I believe there might be some kind of session caching at work, as I will not see an updated database view when I load the page up in another web browser.
How do I fix this issue?
EDIT: I've done some follow up testing, including logging on my controller to make sure my MVC framework (Spring MVC) isn't caching anything. It isn't, even on the controller level it sees out of date database information.
Here is the mapping snippet from my ORM entity file;
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = true)
#Column(name = "ID", nullable = false)
private Integer id;
#Basic(optional = false)
#Column(name = "Name", nullable = false, length = 100)
private String name;
#Basic(optional = false)
#Column(name = "DayOffset", nullable = false)
private int dayOffset;
#Basic(optional = false)
#Column(name = "StartTime", nullable = false, length = 5)
private String startTime;
#Basic(optional = false)
#Column(name = "Enabled", nullable = false)
private boolean enabled;
#Basic(optional = false)
#Column(name = "LastTouched", insertable = false, updatable = false, nullable =
false)
#Temporal(TemporalType.TIMESTAMP)
private Date lastTouched;
#Column(name = "TouchedBy", length = 50)
private String touchedBy;
#JoinTable(name = "ReconciliationSearchRule",
joinColumns = {#JoinColumn(name = "ReconciliationId",
referencedColumnName = "ID", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "SearchRuleId",
referencedColumnName = "ID", nullable = false)})
#ManyToMany(fetch = FetchType.LAZY)
private Collection<SearchRule> searchRuleCollection;
#JoinColumn(name = "ServerId", referencedColumnName = "ID", nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Server server;
#OneToMany(mappedBy = "reconciliation")
private Collection<Report> reportCollection;
I figured out what was going on. My DAO is being injected as a prototype (aka non-singleton) so the backing EntityManager was being created for each use of the DAO. Database changes external to the EntityManager are not registered by queries to that particular EntityManager.
Of course setting the DAO to a singleton causes other issues with the multi-threaded part of my application, but that is a whole other issue.
There are a couple ways of going about this:
Set an eviction strategy using the #DataCache(timeout = 1000) on the entity in question and set it to something reasonable for the object type you are using.
Call evict(...) on your entity or class. The implementation details of this are specific based upon what provider (e.g. Hibernate) you are using.
For example, I will change and persist an object on one page and then go back to the previous page, which lists rows of the database, and it will show the objects as they existed prior to the change.
Second level cache management should be transparent, you don't have to evict() an entity manually after any update, the 2nd level cache gets invalidated for the target table "automatically" by Hibernate.
So, my question is: what do you mean by "going back to the previous page"? Do you mean using the navigator "Back" button? Did you reload the page? Can you clarify this?
Also, can you post your mapping file or annotated entity?