JPA - Using insertable/updatable - java

I am writing a webservice to maintain a database. I am trying to use JPA (EclipseLink) for the entity classes. However, the database uses natural primary keys and therefore there's potential that an update on the ID fields will fail due to foreign key constraints. Our DBA has provided a function to update the ID fields which will create a new parent record with the updated ID, update the child records to point to the new parent and delete the old parent.
If the ID fields could be updated "normally", I would have a situation like this:
#Entity
#Table(name = "PARENT")
public class Parent implements Serializable
{
private static final long serialVersionUID = 1L;
private String parent;
private String attribute;
private Set<Child> childs;
public Parent()
{
}
#Id
#Column(name = "PARENT")
public String getParent()
{
return this.parent;
}
public void setParent(String parent)
{
this.parent = parent;
}
#Column(name = "ATTRIBUTE")
public String getAttribute()
{
return this.attribute;
}
public void setAttribute(String attribute)
{
this.attribute = attribute;
}
#OneToMany(mappedBy = "parentBean")
public Set<Child> getChilds()
{
return this.childs;
}
public void setChilds(Set<Child> childs)
{
this.childs = childs;
}
}
#Entity
#Table(name = "CHILD")
public class Child implements Serializable
{
private static final long serialVersionUID = 1L;
private String child;
private String attribute;
private Parent parentBean;
public Child()
{
}
#Id
#Column(name = "CHILD")
public String getChild()
{
return this.child;
}
public void setChild(String child)
{
this.child = child;
}
#Column(name = "ATTRIBUTE")
public String getAttribute()
{
return this.attribute;
}
public void setAttribute(String attribute)
{
this.attribute = attribute;
}
#ManyToOne
#JoinColumn(name = "PARENT")
public Parent getParent()
{
return this.parent;
}
public void setParent(Parent parent)
{
this.parent = parent;
}
}
I also have a GenericServiceBean class with a method to call functions:
#Stateless
public class GenericServiceBean implements GenericService
{
#PersistenceContext(unitName = "PersistenceUnit")
EntityManager em;
public GenericServiceBean()
{
// empty
}
#Override
public <T> T create(T t)
{
em.persist(t);
return t;
}
#Override
public <T> void delete(T t)
{
t = em.merge(t);
em.remove(t);
}
#Override
public <T> T update(T t)
{
return em.merge(t);
}
#Override
public <T> T find(Class<T> type, Object id)
{
return em.find(type, id);
}
. . .
#Override
public String executeStoredFunctionWithNamedArguments(String functionName,
LinkedHashMap<String, String> namedArguments)
{
Session session = JpaHelper.getEntityManager(em).getServerSession();
StoredFunctionCall functionCall = new StoredFunctionCall();
functionCall.setProcedureName(functionName);
functionCall.setResult("RESULT", String.class);
for (String key : namedArguments.keySet())
{
functionCall.addNamedArgumentValue(key, namedArguments.get(key));
}
ValueReadQuery query = new ValueReadQuery();
query.setCall(functionCall);
String status = (String)session.executeQuery(query);
return status;
}
}
If I set the ID fields to be not editable:
#Id
#Column(name = "PARENT", udpatable=false)
public String getParent()
{
return this.parent;
}
and call parent.setParent(newParent) will this still update the ID in the entity object? How does this affect any child entities? Will they also be updated (or not)?
Another scenario I don't know how to deal with is where I need to update both the ID and another attribute. Should I call the function which updates (and commits) the ID in the database then make calls to set both the ID and attribute via the normal set* methods and then the persistence context will only commit the attribute change?
Perhaps this is a situation where JPA is not appropriate?
Any advice on this is greatly appreciated.

If I set the ID fields to be not editable (...) and call parent.setParent(newParent) will this still update the ID in the entity object? How does this affect any child entities? Will they also be updated (or not)?
updatable=false means that the column won't be part of the SQL UPDATE statement regardless of what you do at the object level so the Id shouldn't be updated. And I'm also tempted to say that child entities shouldn't be affected, especially since you're not cascading anything.
Another scenario I don't know how to deal with is where I need to update both the ID and another attribute (...)
Well, my understanding is that you'd have to call the function anyway so I would call it first.
Perhaps this is a situation where JPA is not appropriate?
I'm not sure raw SQL would deal better with your situation. Actually, the whole idea of changing primary keys sounds strange if I may.

Related

Composite Id JPA2 and Spring Data not working

I had the following entity mapped using JPA 2:
#Entity
public class Translation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String locale;
#Column(name = "business_code",insertable = true,updatable = false,length = 200,nullable = false)
private String code;
private String text;
// Gettets and setters
....
}
Then I realized than the pair (locale,code) should be unique, so I have changed the entity to have an embeddedId composed by locale, code and I removed the column id from the mapping. This way this pair would act as primary key and they could not be repeated:
As a result:
#Entity
public class Translation {
#EmbeddedId
private TranslationId translationId;
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public TranslationId getTranslationId() {
return translationId;
}
public void setTranslationId(TranslationId translationId) {
this.translationId = translationId;
}
#Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
And the embeddedId class:
#Embeddable
public class TranslationId implements Serializable{
private static final long serialVersionUID = 1L;
private String locale;
#Column(name = "business_code",insertable = true,updatable = false,length = 200,nullable = false)
private String code;
#Override
public boolean equals(Object obj){
return EqualsBuilder.reflectionEquals(this, obj);
}
#Override
public int hashCode(){
return HashCodeBuilder.reflectionHashCode(this);
}
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
I'm using Spring data to query the data, so I have modified also my JPA repository to take in account the new composite Id:
#Repository
public interface TranslationRepository extends JpaRepository<Translation,TranslationId> {
}
So, first of all, does anyone see anything wrong here? Should I do it in another way? As my tests are not passing anymore, if I do a simple translationRepository.findAll(), I'm not getting any result (however there is data in the db), but I'm not getting any error message...
And second - if I get this to work, and then I want Spring data to query all the translations only by locale (not by code), how can I do it? As locale and code are now part of the primary key, can I query them independently?
Since your first problem was already fixed, I'll answer the second question
I want Spring data to query all the translations only by locale (not by code), how can I do it?
locale is still accessible as a single property via translationId. In JPQL you can write
SELECT t FROM Translation t WHERE t.translationId.locale = :locale
In Spring Data repository you can either use the #Query on a custom-named method
#Query("SELECT t FROM Translation t WHERE t.translationId.locale = :locale")
public List<Translation> findByLocale(#Param("locale") String locale)
or go with the slightly longer method name, but automatically handled by Spring Data
public List<Translation> findByTranslationIdLocale(String locale)

JavaFX and Data Binding with JPA entities

As the question says, I would like to know the easiest way to perform Data Binding to regular (non JavaFX) properties of JPA entities.
I was thinking there is probably some way to use the same binding functions if you make your entity class implement some interface for change listeners or something of the sort.
Here is an example of a JPA entity with property changed listeners.
#Entity
public class Ticket {
#Id
#GeneratedValue()
private int id;
#ManyToOne()
private EntryGate entryGate;
#ManyToOne()
private ExitGate exitGate;
#Transient
private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
public Ticket() {
}
public Ticket(EntryGate owner) {
this();
this.entryGate = owner;
}
public void addListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public void removeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
public int getId() {
return id;
}
public EntryGate getEntryGate() {
return entryGate;
}
public void setEntryGate(EntryGate entryGate) {
EntryGate oldGate = this.entryGate;
this.entryGate = entryGate;
changeSupport.firePropertyChange("entryGate", oldGate, this.entryGate);
}
public ExitGate getExitGate() {
return exitGate;
}
public void setExitGate(ExitGate exitGate) {
ExitGate oldGate = this.exitGate;
this.exitGate = exitGate;
changeSupport.firePropertyChange("exitGate", oldGate, this.exitGate);
}
}
Here is an example of a JavaFX property binding.
this.idLabel.textProperty().bind(this.ticket.idProperty().asString());
Obviously I can't replace my JPA properties with SimpleXXXProperty... because they are entities participating in object relational mapping.

JPA mapped tree structure with inheritance

I'm trying to implement a tree structure in JPA, that I want mapped to an H2 database using EclipseLink. The nodes of the tree are possibly subclasses of the base node class. What is happening is that EL is creating a brain-dead link table for the children as follows:
[EL Fine]: sql: 2015-04-10 13:26:08.266--ServerSession(667346055)--Connection(873610597)--CREATE TABLE ORGANIZATIONNODE_ORGANIZATIONNODE (OrganizationNode_IDSTRING VARCHAR NOT NULL, children_IDSTRING VARCHAR NOT NULL, Device_IDSTRING VARCHAR NOT NULL, monitors_IDSTRING VARCHAR NOT NULL, PRIMARY KEY (OrganizationNode_IDSTRING, children_IDSTRING, Device_IDSTRING, monitors_IDSTRING))
OrganizationNode is the proper superclass of Device. Both of these are #Entity, OrganizationNode extends AbstractEntity, which is a #MappedSuperclass where the #Id is defined (it is a string). Even stranger, while there is a Monitor class that is not in the tree structure, the only place "monitors" plural occurs is as a field of Device... what??
Now, it's fine to use a table like that to implement a tree structure, but I don't expect a compound primary key with separate instances of the Id field for each subclass! That's got to break - because some children are not Device, and therefore do not have a "Device_IDSTRING", and sure enough:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException|Internal Exception: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "DEVICE_IDSTRING"; SQL statement:|INSERT INTO ORGANIZATIONNODE_ORGANIZATIONNODE (children_IDSTRING, OrganizationNode_IDSTRING) VALUES (?, ?) [23502-186]|Error Code: 23502|Call: INSERT INTO ORGANIZATIONNODE_ORGANIZATIONNODE (children_IDSTRING, OrganizationNode_IDSTRING) VALUES (?, ?)|?bind => [2 parameters bound]|Query: DataModifyQuery(name="children" sql="INSERT INTO ORGANIZATIONNODE_ORGANIZATIONNODE (children_IDSTRING, OrganizationNode_IDSTRING) VALUES (?, ?)")
This seems like truly bizarre behavior. I've tried every combination of mapping annotations I could possibly think of to fix it. Any ideas?
Classes follow.
AbstractEntity.java:
#MappedSuperclass
public abstract class AbstractEntity {
// #Converter(name="uuidConverter",converterClass=UUIDConverter.class)
transient UUID id = null;
#Id String idString;
static long sequence = 1;
static long GREGORIAN_EPOCH_OFFSET = 12219292800000L;
public AbstractEntity() {
ThreadContext tctx = ThreadContext.getThreadContext();
long msb = tctx.getNodeID();
long lsb = (System.currentTimeMillis()+GREGORIAN_EPOCH_OFFSET) * 1000 + ((sequence++) % 1000);
lsb = (lsb & 0xCFFFFFFFFFFFFFFFL) | (0x8000000000000000L);
msb = (msb & 0xFFFFFFFFFFFF0FFFL) | (0x0000000000001000L);
id = new UUID(msb,lsb);
idString = id.toString();
}
#Id
public UUID getUUID() {
return id;
}
public String getIdString() {
return idString;
}
public void setIdString(String idString) {
this.idString = idString;
this.id = UUID.fromString(idString);
}
void setUUID(UUID id) {
this.id = id;
this.idString = id.toString();
}
#Override
public String toString() {
return "["+this.getClass().getCanonicalName()+" "+this.getUUID()+"]";
}
}
OrganizationNode.java:
#Entity
public class OrganizationNode extends AbstractEntity {
#ManyToOne(cascade=CascadeType.ALL)
NodeType nodeType;
#Column(nullable=true)
String name;
#OneToMany(cascade=CascadeType.ALL)
Set<OrganizationNode> children;
public OrganizationNode() {}
public OrganizationNode(NodeType nt, String name) {
this.nodeType = nt;
this.name = name;
children = new HashSet<>();
}
public void setNodeType(NodeType nt) {
nodeType = nt;
}
public NodeType getNodeType() {
return nodeType;
}
public String getName() {
if ((name == null) || (name.equals(""))) return null;
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<OrganizationNode> getChildren() {
return children;
}
public void setChildren(Set<OrganizationNode> children) {
this.children = children;
}
public void addNode(OrganizationNode node) {
children.add(node);
}
public void removeNode(OrganizationNode node) {
children.remove(node);
}
}
Device.java:
#Entity
public class Device extends OrganizationNode {
Set<Monitor> monitors;
public Device() {
super();
}
public Device(NodeType nt, String name) {
super(nt, name);
monitors = new HashSet<>();
}
public Set<Monitor> getMonitors() {
return monitors;
}
public void setMonitors(Set<Monitor> monitors) {
this.monitors = monitors;
}
public void addMonitor(Monitor monitor) {
monitors.add(monitor);
}
}
You need to decide what inheritance startegy you want to use.
The default one is typically the "Single Table Inheritance" so all the subclasses are represented in one table with merged columns.
#Inheritance
#Entity
public class OrganizationNode extends AbstractEntity {
...
}
and you saw it the sql.
You can have Joined, Multiple Table Inheritance where each subclass has its own table and are joined with parent table:
#Inheritance(strategy=InheritanceType.JOINED)
Finally, the last option is Table Per Class Inheritance, where there is no "inheritance" tree reflected in the tables structure, and each object has its full table with all the columns from the class and supperclasses.
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
The last one is the least efficient.
You can have only one strategy, which you define on the top of the inheritance (OrganizationNode), it cannot be changed in subclasses.
The default single table inheritance is typically the most efficient unless there are really a lot of columns which are not shared between the classes
You should probably explicitly declare column which will be used to deteriment the actual class type: #DiscriminatorColumn(name="NODE_TYPE") and for each Entity define the value: #DiscriminatorValue("TYPE1")

Java hibernate/jpa how to create dynamic generic entity that is self related

I would like to create dynamic and generic superclass with JPA/hibernate that will be extended for each hierarchical structured model like: role, page, directory, department, permission, tree.
I would like to create with this object dynamic tree using recursion and java reflection
it should look this way:
This entity should have reference to self entity.
I would like it to be completely abstract and had no db table. Only extendet enities should have db.
I've tried to achive this. But fail so long. Here is my post about it
I consider solutions:
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#Any and #AnyMetaDef mappings
#MappedSuperclass
#Embeddable and #Embedded
I hope someone will give some suggestions.
I came up with the following design. You can also check it on GitHub:
#MappedSuperclass
public abstract class GenericHierarchicalDictionary {
public abstract GenericHierarchicalDictionary getParent();
public abstract Set<? extends GenericHierarchicalDictionary> getChildren();
}
#Entity
#Table(name = "LocalFolder")
public class LocalFolder extends GenericHierarchicalDictionary {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private LocalFolder parent;
#OneToMany(mappedBy = "parent")
private Set<LocalFolder> children = new HashSet<LocalFolder>();
#Override
public LocalFolder getParent() {
return parent;
}
#Override
public Set<LocalFolder> getChildren() {
return children;
}
public void addChild(LocalFolder localFolder) {
localFolder.parent = this;
children.add(localFolder);
}
}
#Entity
#Table(name = "RemoteFolder")
public class RemoteFolder extends GenericHierarchicalDictionary {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private RemoteFolder parent;
#OneToMany(mappedBy = "parent")
private Set<RemoteFolder> children = new HashSet<RemoteFolder>();
#Override
public RemoteFolder getParent() {
return parent;
}
#Override
public Set<RemoteFolder> getChildren() {
return children;
}
public void addChild(RemoteFolder localFolder) {
localFolder.parent = this;
children.add(localFolder);
}
}
And this is the test:
#Test
public void testTree() {
LOGGER.debug("testAddWebResource");
doInTransaction(new TransactionCallable<Void>() {
#Override
public Void execute(Session session) {
LocalFolder rootLocalFolder = new LocalFolder();
session.persist(rootLocalFolder);
LocalFolder localFolder1 = new LocalFolder();
rootLocalFolder.addChild(localFolder1);
session.persist(localFolder1);
LocalFolder localFolder11 = new LocalFolder();
localFolder1.addChild(localFolder11);
session.persist(localFolder11);
RemoteFolder rootRemoteFolder = new RemoteFolder();
session.persist(rootRemoteFolder);
RemoteFolder remoteFolder1 = new RemoteFolder();
rootRemoteFolder.addChild(remoteFolder1);
session.persist(remoteFolder1);
RemoteFolder remoteFolder11 = new RemoteFolder();
remoteFolder1.addChild(remoteFolder11);
session.persist(remoteFolder11);
return null;
}
});
}

play framework 2.0.4, Java : compound primary key declaration and assignment

Using Ebean as ORM, I have the following Model class :
#Entity
#Table(name = "update_proposition")
public class UpdateProposition extends Model {
#EmbeddedId
public UpdatePropositionKey id;
public String fieldName;
public String oldValue;
public String newValue;
#Embeddable
public class UpdatePropositionKey implements Serializable {
#ManyToOne
#JoinColumn(name = "update_request")
public UpdateRequest updateRequest;
public Date date;
#Id
public int serial;
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public boolean equals(final Object obj) {
return super.equals(obj);
}
}
}
My goal is to map a table with a primary key compound by a foreign key, a date and an auto-incremented serial number.
For the instance, this Model throws a RuntimeException: Error reading annotations. How can I implement my use case ?
Once this problem solved, how to assign the date and the foreign key ?
Will a pattern like updateProposition.id.date = Calendar.getInstance().getTime() work fine ?
Thanks for your help.
I found solution to this problem. Your error was because of #ManyToOne annotation in UpdatePropositionKey class. I moved this relation to UpdateProposition class leaving only UpdateRequest.id. So now there are two mappings from UpdateProposition class to UpdateRequest class. one is througt composite key and second through #ManyToOne relation. Both mappings use the same column. Additionally #JoinColumn annotation has attributes 'updatable and 'insertable' set to false. After these all changescode looks as follows:
#Entity
#Table(name = "update_proposition")
public class UpdateProposition extends Model {
public UpdateProposition(int aSerial, Date aDate) {
id = new UpdatePropositionKey();
id.serial = aSerial;
id.date = aDate;
}
#EmbeddedId
private UpdatePropositionKey id;
#ManyToOne
#JoinColumn(name = "update_request_id", insertable = false, updatable = false)
private UpdateRequest updateRequest;
public String fieldName;
public String oldValue;
public String newValue;
public void setUpdateRequest(UpdateRequest aUpdateRequest) {
updateRequest = aUpdateRequest;
id.updateRequest_id = aUpdateRequest.id;
}
public UpdateRequest getUpdateRequest() {
return updateRequest;
}
}
#Embeddable
public class UpdatePropositionKey implements Serializable {
#Id
public int serial;
public Date date;
public int updateRequest_id;
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public boolean equals(final Object obj) {
return super.equals(obj);
}
}

Categories