I need to map a OneToMany relationship in hibernate, with the JPA annotations, in which is involved a weak entity.
For example
Table orders:
CREATE TABLE orders(
idorder serial NOT NULL,
note varchar(30),
CONSTRAINT orders_pkey PRIMARY KEY (idorder)
)
Table OrderItems:
CREATE TABLE orderitems(
idorder integer NOT NULL,
iditem serial NOT NULL,
qnt integer,
CONSTRAINT orderitems_pk PRIMARY KEY (idorder, iditem),
CONSTRAINT fk_orderitems FOREIGN KEY (idorder)
REFERENCES orders (idorder) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Within my class "Orders" I have realized the method getOrderItem() in this way:
// i need cascadeType.All here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "order")
public Set<OrderItem> getOrderItems() {
return items;
}
now,
not being able to know the identifier that will be assigned to a new order
prior to insertion, such as Annotations can I use within the class OrderItem in order to submit automatically (in cascade mode) the correct idOrder?
my goal is to get a situation like this.
Order myOrder = new Order();
// myOrder.setId(1) not necessary
myOrder.setNote("orderNote");
OrderItem firstItem = new OrderItem();
// firstItem.setIdOrder() no need to specify idorder
// firstItem.setId(12);
firstItem.setName("firstItem");
firstItem.setQnt(2);
OrderItem secondItem = new OrderItem();
// secondItem.setId(13);
secondItem.setName("secondItem");
secondItem.setQnt(4);
Set<OrderItem> items = new HashSet<OrderItem>();
items.add(firstItem);
items.add(secondItem);
myOrder.setItems(items);
OrderDAO dao = new OrderDAO();
dao.save(myOrder); // i want inser all items in cascade with the idOder assigned to "myOrder"
Ok I will try to add Entity classes you need to have above given scenario.
#Entity
#Table(name="orders")
public class Order{
#Id
#GeneratedValue
#Column(name="ID")
private Long id;
#Column(name="note ")
private String note ;
#OneToMany(mappedBy="orders")
private Set<OrderItem> orderitems;
// Getter and Setter methods
}
And then the OrderItem class
#Entity
#Table(name="OrderItems")
public class OrderItem{
#Id
#Column(name="iditem")
private Long iditem;
#Column(name="qnt")
private long qnt ;
#ManyToOne
#JoinColumn(name="idorder")
private Order order;
public OrderItem() {
}
// Getter and Setter methods
}
Also i dint get which column your setname maps to.. and iditem isnt generated value its assigned id type
Ok then it also could be issue about inverse that who could be the relationship owener .. Inverse=true is same behaviour as mappedBy attribute usage so try changing it..
Let me know if this works or you get a issue while trying this out..
Related
I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.
I'm getting this error when I try to persist an A class Object:
Detail: Key (classB)=() is not present in table "b".
I need to have the possibility to insert the object with null on the referenced column.
The problem is hibernate convert the null value on an empty string, so when I try to persist the object, it fails.
If I put cascade = CascadeType.ALL on #ManyToOne works, but it creates a row on B table with ID = 0 and an empty string as refColName value. I want to avoid this because de A class is the child, and the cascade should be in B class.
#Entity
#Table
public class A {
...
#ManyToOne
#JoinColumn(name = "class_b", referencedColumnName = "refColName", nullable = true)
private B classB;
...
}
#Entity
#Table
public class B {
...
#Id
#Column (name = "id")
private long id;
#Column(name = "refColName")
private String refColName;
...
}
Any suggestion?
Thanks for your time
Edit:
It's a unidirectional relationship, where B is a master data table, so i have predefined values. refColName should be a String. I use referencedColumnName because I canĀ“t take the id as a foreign key.
I need to have the possibility to insert the object with null on the
referenced column.
Please make sure the classA input to persist should be like below(representing in json)
{
classB:null
}
and not
{
classB:{
refColName:null
}
}
I'm attempting to implement a limited type of object level ACL and its lead me to a place where I'm attempting to create a #OneToOne relationship using a composite key with a constant and dynamic value.
I have an Entity with a database id and a constant value defined in the class.
public class Entity{
private static final int objectType = 1;
#Id
Integer id;
}
I have an access_levels table with a composite key of objectId and objectType.
public class AccessLevel {
#EmbeddedId
private AccessLevelKey accessLevelKey;
#Embeddable
class AccessLevelKey implements Serializable{
private Integer objectType;
private Integer objectId;
....
}
}
Schema of access_levels
CREATE TABLE access_levels(
object_type INTEGER NOT NULL,
object_id INTEGER NOT NULL,
....
CONSTRAINT access_levels_type_id PRIMARY KEY (object_type, object_id)
);
I'm attempting to come up with a one to one relationship that Entity can use to fetch and update its associated AccessLevel
After taking a look a the docs on Non-Standard Joins it seems like I need something like this,
Inside of Entity:
#OneToOne
#JoinColumns({
#JoinColumn(name = "id", referencedColumnName = "object_id"),
#JoinColumn(name = "access_levels.object_type", referencedColumnName = "1"),
})
private AccessLevel accessLevel;
However this throws a hibernate MappingException at app launch
Caused by: org.hibernate.MappingException: Unable to find column with logical name: 1 in access_levels
Thanks!
my problem is that I cannot save my entity because it contains another entity, mapped by a key that is also a part of this table's primary key. The table looks like this:
table C:
+-----+------+
| id_A | id_B |
+-----+------+
..where idA is the primary key of table A with EntityA and idB the primary key of table B with EntityB.
so its basically a n-to-m relation. This is the entity I'm using for table C:
#Entity
public class EntityC {
private long idA;
private EntityB b;
#Id
#Column(name = "id_A")
public long getIdA() {
return idA;
}
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
...setters are here...
}
Please note that id_A is mapped as is (the id), while id_B is mapped as its object representation, EntityB. This is what I want to do with it:
EntityC c = new EntityC();
c.setIdA(123);
c.setB(new EntityB());
em.persist(c);
tx.commit();
em.close();
I want to persist EntityB ONLY IF I can persist EntityC.
on tx.commit() I get this exception: org.hibernate.TransientObjectException: object references an unsaved transient instance
I suppose this happens because part of the primary key, id_B, is not saved. But i set cascading to all so there should be no problem!
Why is this not working?
EDIT:
When I do this:
em.persist(c.getB());
em.persist(c);
it works. But can't Hibernate/JPA do that automatically? I thought that's what cascading is good for.
EDIT2:
added an embeddedId instead of id_A and id_B:
#Embeddable
public class EntityCID implements Serializable {
public long idA;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B", referencedColumnName = "id")
public EntryB b;
}
EntityC now looks like:
#Entity
public class EntityC implements Serializable {
private EntityCID id;
...
#EmbeddedId
public void getId() {
return id;
}
}
but I still get the transient object exception if I don't em.persist(c.getId().b); before em.persist(c). Sticking to that, although it is ugly.
#Trein: it is not bidirectional. EntityB code:
#Entity
public class EntityB implements Serializable {
public long id;
public String text;
}
If you think about it what you are seeing makes perfect sense.
EntityC is is the 'owning side' of the relationship C<>B: it defines the JoinColumn and EntityB has the 'mappedBy' attribute.
So on saving C, order of events would normally be:
insert into C/update C
insert into B/update B
Now in your case this causes issues as obviously C can only be saved if B has been persisted first.
In terms of your statement above: I want to persist "EntityB ONLY IF I can persist EntityC." How can this ever be the case?
JPA has a concept of 'Derived Identifiers', which I am not overly familiar with however is defined in the book Pro JPA as occurring when:
When an identifier in one entity includes a foreign key to another
entity, we call it a derived identifier. Because the entity containing
the derived identifier depends upon another entity for its identity,
we call the first the dependent entity. The entity that it depends
upon is the target of a many-to-one or one-toone relationship from the
dependent entity, and is called the parent entity
Now, despite the original advice that you had two #Id attributes defined and this was wrong it would however appear that having an additional #Id on a 1-2-m is in fact valid in JPA 2 for precisely this case.
The book gives a number of ways of dealing with Derived Identifiers however one example given below looks fairly similar to your case. So you may want to investigate further the #MapsId attribute.
#Entity
public class Project {
#EmbeddedId private ProjectId id;
#MapsId("dept")
#ManyToOne
#JoinColumns({
#JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
#JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")})
private Department department;
// ...
}
#Embeddable
public class ProjectId implements Serializable {
#Column(name="P_NAME")
private String name;
#Embedded
private DeptId dept;
// ...
}
See further:
How do I properly cascade save a one-to-one, bidirectional relationship on primary key in Hibernate 3.6
Is it a bidirectional relationship? I would suggest you to remove #Id getB() and perform the modifications:
#OneToOne(cascade = CascadeType.ALL, mappedBy = "id_B")
#PrimaryKeyJoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
Your entity class must have only one attribute annotated with #Id. Usually when you need this, you create a class that will store both properties and this will act as a Id Class.
You can not pass new Entity() for reference. Because it won't have any values in it(even primary key). So how can hibernate will insert it as foreign key to the table. And cascade will save your parent object if its not saved,no need to call save method for all. But when you passing new object it won't do.
I am trying to learn Hibernate and I could create some simple CRUD operation using a Single Class and Single Table. I am just reading the Hibernate Doc and some online tutorial.
But I have a problem on how to define this relationship with two tables involved. I basically have an Employee table with this structure.
CREATE TABLE EMPLOYEE
(
EMP_ID VARCHAR(10) NOT NULL,
EMP_FIRST_NAME VARCHAR(30) NOT NULL,
EMP_LAST_NAME VARCHAR(30) NOT NULL,
STATUS_ID INT NOT NULL,
PRIMARY KEY (EMP_ID)
);
The STATUS_ID field references another table. STATUS_DESC can either be 'PERMANENT', 'CONTRACTUAL', 'ON-DEMAND'
CREATE TABLE EMP_STATUS
(
STATUS_ID VARCHAR(10) NOT NULL,
STATUS_DESC VARCHAR(100) ,
PRIMARY KEY (STATUS_ID)
);
I am thinking of having an Entity class like this. Now my goal is to return list of Employee object with status, but I don't know how to go about on doing this.
#Entity
public class Employee{
//other private instance
private EmployeeStatus empStatus;
//getters and setters.
}
public class EmployeeStatus{
private int statusID;
private String statusDesc;
//getters and setters
}
You want to know how to map it? ManyToOne?
Employee.java
#Entity
public class Employee{
//other private instance
#JoinColumn(name = "empStatus", referencedColumnName = "yourColName")
#ManyToOne(optional = false)
private EmployeeStatus empStatus;
//getters and setters.
}
Dont forget to change "referencedColumnName" value...
EmployeeStatus.java
#Entity
public class EmployeeStatus{
#Id //this is your pk?
private int statusID;
private String statusDesc;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "empStatus", fetch = FetchType.LAZY) //or EAGER
private List<Employee> empList;
//getters and setters
}
To create a relationship between two tables you need to decide:
Is the relationship bi-directional? That is, do the statuses know the employees or not? If no then it is uni-directional. In that case you can add the annotation on the Employee class like this:
#ManyToOne
#JoinColumn(name = "status")
private EmployeeStatus empStatus;
And there is a few other options that you may add.
You can do what you are doing, but I would suggest, if the status can only be one of three values, create an Enum with the three values. No need for a separate table.
The downside for this is you need to create a hibernate custom type (the code is on the wiki) to support persisting enums.
A simpler answer is to not use a secondary table, and just save the status as a String on the domain object. You can put business logic on your model to ensure the String is in the list of acceptable values.
If you really want to use a relationship between two entities, then check out the hibernate docs on many-to-one relationships.
You can use HQL to query the entities. Like so
Query q = s.createQuery("from Employee as e where e.empStatus = :status");
q.setParameter("status", status);
List emps= q.list();