I have two classes
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="PERSONTYPE")
#DiscriminatorValue(value="PERSON")
public class Parent {
.......
}
#Entity
#DiscriminatorValue(value="CHILD")
public class Child extends Parent{
.......
}
The scenario I have:
create a person -- then the PERSONTYPE = 'PERSON'
go the Person page and update it to be 'CHILD' by checking a check box 'Is Child' then after save the Person must be saved to be with type 'CHILD'.
Then how can I change the entity type from 'PERSON' to 'CHILD'?
Here are a couple possibilities:
It seems the obvious thing to do would be to setIsChild(true) on the Parent object and commit it, however I'm not sure how JPA will react to this since you are now committing a Parent and the result is a Child. Not sure this is possible. It is definitely worth a try.
Another option would be to write JPA update statement (circumventing Child and Parent objects) to update the is_child column in the database. Then when you subsequently query this record you will get a Child back not a Parent.
Lastly, you could create a child object with all values of the parent object, then delete the parent, and create the child. This will work, however aside from the extra processing required for delete / create, instead of a simple update, the id of the child may change (it will change if you are using auto generated ids). IMO, this is not a good solution, but it will work.
i assume you have unique properties for Child Object and thats why you want to use inheritance, otherwize just as #ZB Ziet commented you should just use child flag
Solution 1
i see you are using single table inheritance strategy, thus you have to modify the descriminator field manulay (by using SQL queries) and set appropirate fields on child table.
UPDATE Parent SET PERSONTYPE='CHILD' WHERE id = 1
practicaly you use native queries like this
enityManager.createNativeQuery(“UPDATE PERSON SET PERSONTYPE = ?, ”
“ VERSION = VERSION + 1 WHERE ID = ?”)
.setParameter(1, 'CHILD')
.setParameter(2,personID)
.executeUpdate();
then you can use entitymanager to get the child and set properties
entityManager.find(Child.class,1).childProp=xxxx
** Solution 2 **
IMO, The best thing to do here is instead of using single table strategy you should use joined table strategy
in joined table strategy new table entireis is created for each child having thier id as foreign key to parent entity.so changing the id will also set its parent
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Person {
...
it would have been cool if you can just create new child set the id same as parent and save. Child c = new Child(); c.setId(parent.getId()); entityManager.merge(c) . but hibernate tries to re-create a parent object resulting id confilict.
so the solution is to write native query
em.createNativeQuery("INSERT into child (id) VALUES (1)").executeUpdate(); //1 being the parent id
more reference on inheritance
Use EntityMaster
The Idea is to create an Entity just for changing the discriminator.
At least this Entity need to have the id and the discriminator.
#Entity
public class ParentMaster {
public static final PERSON="PERSON";
public static final CHILD="CHILD";
private Long id;
private String persontype;
//getter setter
}
Now load the ParentMaster by id and pm.setPersontype(ParentMaster.CHILD);.
Related
I guess I don't really understand bidirectional one-to-many relations.
I have a parent, a child-A and child-B class with bidirectional relations between Parent <-> Child-A <-> Child-B. I want to add a child-B to the child-B-list of the child-A, but on every try to merge the parent I get an dublicated key exception. So how do I add a new child-B.
class Parent {
long id;
#One-To-Many, MappedBy Parent, Cascade All, OrphanRemoval true
list <child-A> childsA;
...
setter + getter
}
and
class Child-A {
long id;
#Many-To-One, JoinColums parentId, Cascade PERSIT MERGE REFRESH
Parent parent;
#One-To-Many, MappedBy Child-A, Cascade All, OrphanRemoval true
list <child-B> childsB;
...
setter + getter
}
and
class Child-B {
long id;
#Many-To-One, JoinColums Child-A-id, Cascade PERSIT MERGE REFRESH
Child-A child-A;
...
setter + getter
}
How do I add a new child-B to child-A and merge the parent to save everything in the db? So far I've tried:
Parent p = entityManager.getParent();
Child-A ca = p.getChildsA.get(indexOfCa); // the index is known
Child-B cb = new Child-B ();
... // fill cb with information
ca.add(cb); // automatically fires cb.setChild-A(ca);
p.getChildsA.set(index, ca);
entityManager.merge(p);
But this causes a DublicatedKeyException. So what is the best practise to add a child-B object to an already persisted child-A-object from an parent-object?
I also have to say, only merge is possible (no save, saveOrUpdate or persist) and it is not possible to edit the entity-classes. The entities are generated by something like a factory and every change will be overwritten when building the project. And the pom-file is not editable.
Also this is a Java EE web-application with many different frameworks like primefaces and omnifaces.
Solved it by myself. The limit for auto generated primary keys on the mysql database where to low. So jpa run out of primary keys and tried to override existing keys. Increased the hibernate_sequence number and the exception was gone.
My problem is to persist two classes that have a 1:n relationship:
public class DayRecord {
private Long id;
private List<TimeRecord> timeRecordsToday = new ArrayList<TimeRecord>(4);
...
}
public class TimeRecord {
private Long id;
...
}
So, in code, DayRecord knows TimeRecord.
create table DAY_RECORDS (
id int primary key,
);
create table TIME_RECORDS (
id int primary key,
day_record_id int not null,
foreign key (day_record_id) references DAY_RECORDS (id)
);
In database, TimeRecord knows DayRecord.
Can I save a DayRecord with all its TimeRecords in one step?
In Hibernate, I can set an inverse mapping and just save a DayRecord and all its TimeRecords will get saved, too. With MyBatis, I tried to save the classes independently from each other:
<mapper
namespace="de.stevenschwenke.java.javafx.xyz.DayRecordMapper">
<insert id="insertDayRecord"
parameterType="de.stevenschwenke.java.javafx.xyz.DayRecord">
insert into DAY_RECORDS (id) values (NEXT VALUE FOR DAY_RECORDS_SEQ);
</insert>
</mapper>
<mapper
namespace="de.stevenschwenke.java.javafx.xyz.TimeRecordMapper">
<insert id="insertTimeRecord"
parameterType="de.stevenschwenke.java.javafx.xyz.TimeRecord">
insert into TIME_RECORDS (id) values (NEXT VALUE FOR TIME_RECORDS_SEQ);
</insert>
</mapper>
But how can I save the DayRecord-ID inTimeRecord?
Ideas:
Give TimeRecord an attribute dayRecordId. This way, a cyclic dependency would be created. However, the mapping would take care of the dpenedency while saving.
In one transaction, save the DayRecord first, get its ID, set it in TimeRecords and save this object.
use a nested select-statement within insert like in the documentation
What is the best way to save both objects? Thanks for your help!
As jdevelop already mentioned, MyBatis is just a SQL wrapper. Because SQL doesn't offer a way to insert two objects that have a relationship, MyBatis can't do that either.
So here's my workaround: As I mentioned, I don't want to add a circular dependency by letting TimeRecord know about DayRecord. So I created a wrapper class just for inserting TimeRecords:
public class TimeRecordInsertWrapper {
public Long id;
public int hours;
public long dayRecordId;
[constructor/getter/setter omited but there with public access modifier]
}
First, I store the DayRecord and get it's ID. Then I create the wrapper object and store the TimeRecords:
public long insertDayRecord(DayRecord newRecord) {
SqlSession session = sqlSessionFactory.openSession();
try {
session.insert(
"de.stevenschwenke.java.javafx.xyz.DayRecordMapper.insertDayRecord",
newRecord);
for (TimeRecord tr : newRecord.getTimeRecordsToday()) {
TimeRecordInsertWrapper wrapper = new TimeRecordInsertWrapper(tr.getHours(), newRecord.getId());
session.insert("de.stevenschwenke.java.javafx.xyz.TimeRecordMapper.insertTimeRecord",
wrapper);
}
return newRecord.getId();
} finally {
session.commit();
session.close();
}
}
This way, I can use my nice one-way object model AND have the "right" mapping in the database.
Mybatis is just SQL mapping framework, it allows you to abstract SQL code from Java code and that's it, more or less. They are pretending to look like Hibernate with recent versions, but this leads to weird constructions in XML.
I would suggest to store the DayRecord and get it's it from selectKey, then use that ID in subsequent calls to the mapper. This is what actually happens inside the mapper, but complex XML implies complex FSM to built inside. So keep it simple and you're safe with myBatis, or use Hibernate.
What is even better, you can define custom DAO interfaces for the tasks, and then you can have some sort of Service layer with #Transactional attribute set. This requires mybatis-guice, but it works really great and you don't need to deal with transactions in your code (they are declarative).
Given the following example:
public class Parent
{
public Guid ID {get;set;}
public string Name {get;set;}
public Child Child {get;set;}
}
public class Child
{
public Guid ID {get;set;}
public string Name {get;set;}
}
I want to save a Parent object instance, with a Child instance assigned to it. The fact here is: the given child object already exists in the Database,
Parent p = new Parent();
p.ID = Guid.NewGuid();
// Get the Child Object
Child c = GetTheChild(...);
p.Child = c;
SaveParent ( p );
in function Save Parent, the following code is implemented:
public void SaveParent ( Parent p )
{
using (MyContext context = new ClinicContext())
{
context.Parents.Add( P );
context.SaveChanges();
}
}
Now the problem comes: since the child in this example already exists in the database, but when adding the parent in the context DBSet, the entity c given also holds a entry state of "Added", guess what? DBContext tries to save another child record with a duplicated Key!
Any body know how to solve this? I am thinking whether there is a way to turn a Insert into Update if the Entity Framework can detect the records already exists in the database.
Thanks for your help.
If you add an entity all releated entity will be added. If you attach an entity all related entities will be attached. In your case you want to add the Parent entity and then attach the child entity. What you could also try would be to use foreign keys and add parent and just set the foreign key to the related child Id. This way you would not have to bring the entity to the client to create the relationship (you need to know the key of the related entity though).
How are you getting the child? The child should be attached to the same context you save the parent on.
My guess is your GetTheChild function uses a new context - which means when you return the child is detached and for all intents and purposes EF figures its been 'added'
As panel said you can just add a foreign key and just set the child's key instead of the child itself but there seems to be some design issues you should address.
I guess GetTheChild() will return a child from database. So, all you need to do is call ChangeObjectState method to mark the child as Unchanged before calling SaveChanges.
var osm = context.ObjectStateManager;
osm.ChangeObjectState(child, EntityState.Unchanged);
Sorry if duplicated.
Is it possible or recommended for business layer to using objects instead of ids?
SELECT c
FROM Child AS c
WHERE c.parent = :parent
public List<Child> list(final Parent parent) {
// does parent must be managed?
// how can I know that?
// have parent even been persisted?
return em.createNamedQuery(...).
setParameter("parent", parent);
}
This is how I work with.
SELECT c
FROM Child AS c
WHERE c.parent.id = :parent_id
public List<Child> list(final Parent parent) {
// wait! parent.id could be null!
// it may haven't been persisted yet!
return list(parent.getId());
}
public List<Child> list(final long parentId) {
return em.createNamedQuery(...).
setParameter("parent_id", parentId);
}
UPDATED QUESTION --------------------------------------
Do any JAX-RS or JAX-WS classes which each can be injected with #EJB can be said in the same JTA?
Here come the very original problem that I always curious about.
Let's say we have two EJBs.
#Stateless
class ParentBean {
public Parent find(...) {
}
}
#Stateless
class ChildBean {
public List<Child> list(final Parent parent) {
}
public List<Child> list(final long parentId) {
}
}
What is a proper way to do with any EJB clients?
#Stateless // <<-- This is mandatory for being injected with #EJB, right?
#Path("/parents/{parent_id: \\d+}/children")
class ChildsResource {
#GET
#Path
public Response list(#PathParam("parent_id") final long parentId) {
// do i just have to stick to this approach?
final List<Child> children1 = childBean.list(parentId);
// is this parent managed?
// is it ok to pass to other EJB?
final Parent parent = parentBean.find(parentId);
// is this gonna work?
final List<Child> children2 = childBean.list(parent);
...
}
#EJB
private ParentBean parentBean;
#EJB
private ChildBean childBean;
}
Following is presented as an answer only to question "Is it possible or recommended for business layer to using objects instead of ids?", because I unfortunately do not fully understand second question "Do any JAX-RS or JAX-WS classes which each can be injected with #EJB can be said in the same JTA?".
It is possible. In most cases also recommended. Whole purpose of ORM is that we can operate to objects and their relationships and not to their presentation in database.
Id of entity (especially in the case of surrogate id) is often concept that is only interesting when we are near storage itself. When only persistence provided itself needs to access id, it makes often sense to design methods to access id as protected. When we do so, less noise is published to the users of entity.
There is also valid exceptions as usual. It can be for example found that moving whole entity over the wire is too resource consuming and having list of ids instead of list of entities is preferable. Such a design decision should not be done before problem actually exists.
If parent has not been persisted yet, then the query won't work, and executing it doesn't make much sense. It's your responsibility to avoid executing it if the parent hasn't been persisted. But I would not make it a responsibility of the find method itself. Just make it clear in the documentation of the method that the parent passed as argument must have an ID, or at least be persistent. No need to make the sameverification as the entity manager.
If it has been persisted, but the flush hasn't happened yet, the entity manager must flush before executing the query, precisely to make the query find the children of the new parent.
At least with Hibernate, you may execute the query with a detached parent. If the ID is there, the query will use it and execute the query.
I have an object that has been populated with the contents of four different related entities. However i have another entity in which i cannot include as part of the query due to it not being related in the navigation properites directly to the IQueryable table i am pulling. The entity i am trying to include is related to one of the four different entities that have been included successfully.
Is there a way to include(during db hit or afterwards) this entity as part of the overall object i am creating?
Here is an example of what my calls look like to build the CARTITEM object:
public List<CARTITEM> ListCartItem(Guid cartId)
{
//Create the Entity object
List<CARTITEM> itemInfo = null;
using (Entities webStoreContext = new Entities())
{
//Invoke the query
itemInfo = WebStoreDelegates.selectCartItems.Invoke(webStoreContext).ByCartID(cartId).ToList();
}
//Return the result set
return itemInfo;
}
here is the selectCartItems filter(Where i would normally do the includes):
public static Func<Entities, IQueryable<CARTITEM>> selectCartItems =
CompiledQuery.Compile<Entities, IQueryable<CARTITEM>>(
(cart) => from c in cart.CARTITEM.Include("ITEM").Include("SHIPPINGOPTION").Include("RELATEDITEM").Include("PROMOTION")
select c);
from this i have my CARTITEM object. Problem is i want to include the PROMOTIONTYPE table in this object, but since the CARTIEM entity doesn't have a navigation property directly to the PROMOTIONTYPE table i get an error.
Let me know if you need any more clarification.
Thanks,
Billy
You can use join and if it is the same database and server it should generate the join in SQL and do it all in one call...
LinqToEnties join example