ElementCollection with same collectiontable - java

I want to store emailIds that needs to be notified when a change happens into a table.
I only have one table with columns ownerid and emailid. I want to store emailid in separate rows, rather than a comma separated list. I don't need a owner table, because i dont have any other information for the owner. I just want to save the emailids, but into separate rows.
how do i do that in hibernate.
I tried using something like this
#Table(name = "OwnerEmailIds")
#Entity
public class OwnerEmailIds implements Serializable {
private static final long serialVersionUID = 5906661729869048121L;
#Column(name = "OwnerID", nullable = false)
private String ownerId;
#ElementCollection(fetch=FetchType.EAGER) // must use eager as we close the session before returning
#CollectionTable(name="OwnerEmailIds", joinColumns=#JoinColumn(name="vendorId"))
#Column(name = "emailId")
private Set<String> EmailIds = new HashSet<String>();
But problem here is my collection table is same as the entity table.
And as my emailIds column in not null, when i try to save the object it fails with sql error stating emailid column can't be null.

To be able to map an entity to a table, Hibernate requires a unique id property, that maps to a unique (primary) key on the table.
As you have stated that you don't have this and if this is the only table your application is using, then Hibernate might not be the best choice.
You could still get the required information through Hibernate from your database by executing a native SQL query:
SQLQuery q = session.createSQLQuery("select OwnerID from OwnerEmailIds where emailId = :email");
q.setScalar("OwnerID", StandardBasicTypes.LONG);
q.setParameter("email", emailId)
List<Long> ids = (List<Long>) q.list();
Storing the ids could also be done with a native query, but here you would have to take into account data that is already there, so it is a little harder.

I think what you are asking for is to put collection in multiple rows and repeat other values, in you case email list will be inserted and owner_id will repeated for it's emails.
Hibernate does not provide any configuration for that. You have to do it manually.
You can do what you are looking for using the following structure and code. You must have a primary key, here you can make your email id as primary key or you can add an autoincrement integer id.
#Table(name = "OwnerEmailIds")
#Entity
public class OwnerEmailIds implements Serializable {
private static final long serialVersionUID = 5906661729869048121L;
#Column(name = "OwnerID", nullable = false)
private String ownerId;
#Id
#Column(name = "emailId")
private String emailId;
//setter and getters
}
Then you iterate through the list of email ids and create the below object and then save them individually. You can not create a collection object and configure hibernate to store them in the current table in multiple rows.
//loop throught the list of email ids
OwnerEmailIds oei = new OwnerEmailIds();
oei.setOwnerId("first last name");
oei.setEmailId("some#somedomain.com");
session.save(oei);
If you make emailId as primary key then you will have to make sure that no two owners has same email id. If that is the case then you have to add another auto_increment primary key.

Related

Hibernate - 1:N - The parent is duplicated upon saving the child

GitHub repo if needed, Maven web project (pom.xml), SQL script under resources directory.
I'm aware this is my fault, the problem is I haven't been able to fix it in the entire day, its likely something simple and over my head, also please ignore the relations regarding the table names and columns, it's a sample project to show the problem.
Expected:
Store the new child along its parent relation (the child has a column for it), without storing the parent again.
Error:
CascadeType.ALL causes the parent to duplicate, but attempting to remove it to use the other types throws: java.sql.SQLIntegrityConstraintViolationException: Column 'user_id' cannot be null
Column 'user_id' is the name of the column in the child table that stores the parent relation.
I will skip some annotations among other things so this doesn't become a wall of code
User entity
private Long id;
private String name;
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = {
CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH})
private List<Username> usernameList = new ArrayList<>();
Username entity
private Long id;
private String username;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
Again, using anything but CascadeType.All throws an error for some reason
UsernameDAO (this is the method being used to store the child, username, which duplicates the parent, user)
Session session = factory.getCurrentSession();
session.save(username);
UsernameService
usernameDAO.save(username);
I believe the way you are assignment the object User in the object Username is incorrect. If you are doing something like this:
User user = new User();
user.set(... // set your attributes
Username username = new Username();
username.set(user);
Now if you save the username, hibernate will create a entry in the database for user because the way you have created user is by using the new keyword and this will make a new entry in the db.
If you don't want to create a new entry for the user, then you will have to load the entity from the database, so you should add a new method in your service class User which will return you a user given a user id.
e.g:
User user = userService.getUser(10);
Username username = new Username();
username.set(user);
Now when you will save username, this will not create a new entry in the table User. This is the way hibernate works. We have to load the entity, then do our operation on it and save it. The new keyword will create a new entry even if the id of the entity (primary key in db) is the same.
Please let me know if it's wrong to answer one's own question, so I just remove this.
The problem was in the front-end, I had the following in my Spring Form:
<form:select path="user" items="${listUsers}" />
When it should of been
<form:select path="user.id" items="${listUsers}" itemValue="id"/>
1) If itemValue is not specified Spring will take the value from toString.
2) Use the id field (binded entity) as the itemValue and the relationship field id as the path
3) You don't necessarily have to remove ClassType.PERSIST in order to avoid duplicates, as long as your entities are binded by their unique identifier (like the id), otherwise hibernate will see it as a new entry.

Why isn't the foreign key field of a Many-to-One relationship being set on insert?

My Spring web application allows users to update "Employee" records to change the fields or add new "Phone" records related to this "Employee" record. However, when the "Employee" record is submitted for update after adding a new "Phone" record, it's throwing a SQL error exception.
The problem is that the "employee_id" foreign key on "Phone" table to the "Employee" table isn't set in the eventual SQL insert statement submitted to the database. However, in the "PhoneEntity" JPA entity object that is referenced by the updated/merged "EmployeeEntity" object, the property associated with the employee_id database field isn't null, it's set to the "EmployeeEnity" Object being updated/merged.
From my understanding of JPA, having the entity property associated with a database field should set it when the insert statement for the entity's record is submitted to the database, but in this case it isn't which is causing this error.
I've tried stepping through with a debugger, and I have verified that the created PhoneEntity object is a member of EmployeeEntity's phones property, and that the same PhoneEntity's employee property is set to the same EmployeeEntity object (with the same object IDs) in a bidirectional relationship.
I've also set the hibernate.show_sql=true to see the SQL statement being submitted to the database and it includes the statement (with the ellipses being more fields):
Hibernate:
insert
into
phone
(id, employee_id, ...)
values
(?, ?, ...)
Which means that it is inserting a new phone for the new PhoneEntity object.
After trying to running this insert statement it gives the SQL error "Column 'employee_id' cannot be null". However like I said before, I've checked with the debugger and the employee property is indeed set to the EmployeeEntity object.
this is a simplified example of what my code looks like:
#Entity
#Table(name = "employee")
public class EmployeeEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST})
private Set<PhoneEntity> phones = new HashSet<>();
...
}
#Entity
#Table(name = "phone")
public class PhoneEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#ManyToOne
#JoinColumn(name = "employee_id", nullable = false)
private EmployeeEntity employee;
...
}
With tables that have the structure created by the following SQL statements.
CREATE TABLE employee (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
...
);
CREATE TABLE phone (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
employee_id INT NOT NULL,
...
FOREIGN KEY(employee_id) REFERENCES employee(id)
);
And the following is where it actually submits the updates to the entity manager to make updates to the database.
public void update(EmployeeDomain employee) {
EmployeeEntity entity = employeeDomainToEntity.transform(employee)
getEntityManager().merge(entity);
}
The EmployeeEntity and PhoneEntity objects are created by converting similar domain objects that were in turn deserialized from a http request. I'd include more of this section of the code but, as I've mentioned, I've already confirmed with my debugger that the actual entity objects being submitted to the merge are already in the form that we expected with the phones fields and employee fields being set correctly, so the end entities should be correct.
In the official JPA specification document (version 2.1) in section "3.2.7.1 Merging Detached Entity State" (page 85) we find:
For all entities Y referenced by relationships from X having the cascade element value cascade=MERGE or cascade=ALL, Y is merged recursively as Y'. For all such Y referenced by X, X' is set to reference Y'. (Note that if X is managed then X is the same object as X'.)
This explains that you are lacking cascade=MERGE for the annotation of the phones field.
As proposed in thanh ngo's answer, the aforementioned definition (or: explanation) thus translates to:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();
Alternatively, you could also make use of cascade=CascadeType.ALL. However, this would also include operations such as CascadeType.REMOVE which might not always be intended.
Hope it helps.
I think the problem is that you are using merge.
The cascade type setting for the entity should be:
#OneToMany(mappedBy="employee", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<PhoneEntity> phones = new HashSet<>();

Insert attribute in table if it is not null using Hibernate

I have mapping class defined as:
#Table(name = "TEST_TABLE")
public class DBTestAccount
{
#Id
#Column(name = "UUID", nullable = false, length = 36)
private String uuid;
#Column(name = "REGION")
private String region;
#Column(name = "COUNTRY")
private String countryCode;
//getters and setters
}
Now I need to update the table. For that let's say I create following object:
DBTestAccount dbTestAccount = new DBTestAccount();
dbTestAccount.setUuid("testUUID");
dbTestAccount.setRegion("testRegion");
dbTestAccount.setCountryCode(null);
Now let's say initially in the table we have a record that has some value of COUNTRY. Inserting the above object will replace the value and make COUNTRY null. I want that it should update the data, but if the column is null, then it should ignore and do not update it. If it is non-null then it should update it. How to achieve this in hibernate? Is there an annotation to do so? If not then what is the possible solution (except using if - else). Can I create a custom annotation for this?
PS:
The underlying database is PostgreSQL.
The example you are describing can't be present in the database, because the object is not an entity yet, as it is created with new keyword and it isn't yet persisted in the database.
From your explanation, what I got, is that you want to save only changed attributes. For that purpose hibernate has the Dynamic Update annotation.

Native Query (JPA ) not reset and return the same old result

I have a native sql query as the following :
for (init i=0; i<=2 ; i++) {
String sql = "Select * from accounts where id = ?";
Query query = em.createNativeQuery(sql,AccountBean.class);
query.setParameter(1, i );
AccountBean accountBean = (AccountBean)query.getSingleResult();
}
For the first loop it works correctly but any loop after the first one returns the same result as the first one , i debug it, the parameter changed , it works correctly if i change
Query query = em.createNativeQuery(sql,AccountBean.class);
to
Query query = em.createNativeQuery(queryString);
Regards
Wish79
Every JPA entity must have a primary key. Your JPA entities may not properly reflect the primary key, if any, on the database table.
I ran into the same problem. In my model class I had only one class variable annotated with #Id. However, that was not an accurate reflection of the table itself, which has a composite primary key. Thus, my query results returned the correct number of rows, but each confoundingly contained the same values, even though the actual data was different in the db. For example, this query:
Query query = entityManager.createQuery
("SELECT tbl FROM Tbl tbl WHERE tbl.id = 100
and tbl.code in ('A','B','C')");
...returned 10 rows, each showing a code of 'A'. But in actuality 9 of those 10 rows had a different code value ('B' or 'C'). It seemed as if the results were being cached and/or the tbl.code predicate was ignored. (That happened whether I used JPQL or Native SQL.) Very confusing.
To fix this I added an additional #Id annotation to my model to reflect the composite primary key:
#Id
#Column(name = "Code")
public String getCode() {
return this.code;
}
Now the query returns the data correctly and the code select criteria is no longer effectively ignored.
Edit: Although the above worked for me, on further research it seems a better approach to configure a separate JPA Entity composite primary key class. See http://docs.oracle.com/cd/E16439_01/doc.1013/e13981/cmp30cfg001.htm.
For example, here's an Entity class with an embedded primary key (see #EmbeddedId):
/**
* The persistent class for the SOME_TABLE database table.
*/
#Entity
#Table(name = "SOME_TABLE")
public class SomeTable implements Serializable {
#EmbeddedId
private SomeTablePk id;
#Column(name = "NUMBER_HRS")
private BigDecimal numberHrs;
...
...and here's the composite primary key class (see #Embeddable):
#Embeddable
public class SomeTablePk implements Serializable {
#Column(name = "SOME_ID")
private String someId;
#Column(name = "ANOTHER_ID")
private BigDecimal anotherId;
public String getSomeId() {
return someId;
}
...

Delete a referenced/OneToMany relation instead of "Nulling" the column

We have two entities:
public class User {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id", insertable = true, updatable = true)
#ElementList(name = "tabledata")
public List<MyUserTableData> tableData = new ArrayList<MyUserTableData>();
}
public class MyUserTableData {
public Long user_id;
}
The action that I do is that I remove an entry from u.tableData and then call the EntityManager to merge(u).
OpenJPA will remove the entry from the User object by setting the corresponding record in the MyUserTableData with a user_id = "null".
But what I want is that if the entry is deleted from the User, it should also delete the record from the MyUserTableData and not just NULL the column user_id.
How can I force OpenJPA to delete the OneToMany related entry instead of putting a null in the column?
I will not accept answers that do asume that my database schema is bad :) The table MyUserTableData is basically a foreign key table that connects the user to another entity but holds some more information then just a foreign key, it adds some meta data to the foreign key that neither belong to the user nor to the other entity.
Thanks!
Sebastian
I was able to resolve my issue:
http://openjpa.apache.org/builds/1.0.4/apache-openjpa-1.0.4/docs/manual/manual.html#dependent
#ElementDependent => does exactly what I want.

Categories

Resources