How to use ORMLite foreign objects WITHOUT generated id? - java

I am learning how to use ORMLite with android. My problem is that I receive objects with an ID from the server and I think it would be good to use the same ID for my DB. This means I am not using generatedId = true and therefore cannot use foreignAutoGenerate = true.
public class Artwork {
#DatabaseField(id = true, columnName = "id")
String id;
#DatabaseField
String name;
#DatabaseField
String file;
#DatabaseField(columnName = "user_id", foreign = true, foreignAutoCreate = true)
User owner;
}
As you can see, Artwork references the user who owns it. Both already have IDs on the server side that I would like to use as IDs for my DB.
public class User {
#DatabaseField(id = true, unique = true)
String id;
#DatabaseField
String name;
}
And below is where the magic should happen...
Artwork artwork = new Artwork();
artwork.setName("name");
artwork.setFile("filepath");
artwork.setId("generated_by_server_0000");
User owner = new User();
owner.setId("generated_by_server_0001")
owner.setName("user");
artwork.setOwner(owner);
DatabaseHelper dbHelper = OpenHelperManager.getHelper(this, DatabaseHelper.class);
Dao<Artwork, String> artworkDao = dbHelper.getArtworkDao();
Dao<User, String> userDao = dbHelper.getUserDao();
userDao.create(owner);
artworkDao.create(artwork);
List<Artwork> artworksOnDb = artworkDao.queryForAll();
How can I easily persist those objects using ORMLite but setting the ID myself?

My problem is that I receive objects with an ID from the server and I think it would be good to use the same ID for my DB. This means I am not using generatedId = true and therefore cannot use foreignAutoGenerate = true
Right. You don't not have to do generatedId = true with a foreign object but unfortunately you do need to do it with foreignAutoCreate = true because otherwise ORMLite wouldn't know if the User needs to be created or not. If you are using your own id, you'll need to use the UserDao and create the User directly and not rely on the auto mechanism.
To quote the docs for foreignAutoGenerate:
Set this to be true (default false) to have the foreign field automatically created using an internal DAO if its ID field is not set (null or 0). So when you call dao.create() on the parent object, any foreign field that has this set to true will possibly generate an additional create call via an internal DAO. By default you have to create the object using its DAO directly. By default you have to create the object using its DAO directly. This only works if generatedId is also set to true.
One thing that it is important to realize is that you have to insert the User before you insert the Artwork because the Artwork stores a user_id in its table.
User owner = new User();
owner.setId("generated_by_server_0001")
owner.setName("user");
...
// do this _before_ the create of Artwork
userDao.create(owner);
Artwork artwork = new Artwork();
artwork.setName("name");
...
artwork.setOwner(owner);
artworkDao.create(artwork);

Related

Hibernate add an entity with foreign key using REST

First of all, I haven't written any SQL statements to create a table. I try to use Hibernate/jpa only without writing SQL.
My relation is in the following: A user can have many task, a task only has one user.
I created my models as this:
User Table:
#Entity
#Table(name="T_USER")
#EntityListeners(AuditingEntityListener.class)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "uid")
private Long uid;
...
}
Task Table:
#Entity
#Table(name = "T_TASK")
#EntityListeners(AuditingEntityListener.class)
public class TASK{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long tid;
#ManyToOne
#JoinColumn(name ="oid")
private User owner;
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
...
}
The relation is task's ownerid(oid) is user's uid.
To save a user to my database, I'm using postman with the following parameters:
{
"username": "firstuser",
"email": "firstuser#email.com"
}
To save a task to my database I'm using this:
{
"description": "some description",
"oid": "12" // I also can state username of the user rather than ID
}
However, as I execute save a task, the oid of task is NULL. In data access objects, I have:
#PostMapping("/save")
public QR createTask(#Valid #RequestBody Task task)
{
return taskDAO.save(task);
}
1-What am I doing wrong? I just want to add a task with owner id to database, however it returns null as ownerid.
2-Should I create a table first with SQL using
Create table task(
tid BIGINT,
description VARCHAR(255),
oid BIGINT,
PRIMARY KEY(tid), FOREIGN KEY(oid) REFERENCES (user.uid))
3-Should I change my save method in TaskDAO?
public Task save(Task task)
{
return taskRepository.save(task);
}
4- Should I change my controller method(createTask method using RESTcall)
5- Assume that all of the problems above is fixed. How can I fetch all task that a user has?
6- How can I delete a task when a user is deleted(cascase in SQL, but is there any method in Hibernate)
I hope I explained my problem. Any feedback will be appreciated.
1-What am I doing wrong? I just want to add a task with owner id to database, however it returns null as ownerid
First of all, I would make sure that the owner is being persisted in the db, just to be sure that you have a value to be referencing
2-Should I create a table first with SQL using
Since you're using ORM, writing an SQL query would defeat the purpose of that, you could, but it's not all that necessary, since the relationships are specified already
3-Should I change my save method in TaskDAO?
4- Should I change my controller method(createTask method using RESTcall)
I think it would be best to change your createTask method, you could include the user's id as a pathvariable or a queryparameter and in that method you find the user using their id and set the user field in the task before passing it to the dto to save the value.
The reason the oid is null is because you do not have such a field in there.
5- Assume that all of the problems above is fixed. How can I fetch all task that a user has?
In your task repository, you can create a method like
Collection<Task> findAllTasksByOwnerId(Long id);
6- How can I delete a task when a user is deleted(cascase in SQL, but is there any method in Hibernate)
You can specify the cascade type where you have specified the relationship between the task and the user
You can check this link for a simple tutorial on how to cascade in spring
The oid is null because there is no such field in Task. I think you are mixing two concepts here. The first one is the data transfer object that represents your REST data structure. This one should have an oid field. The second one is the persisting entity. This one you have, it's the Task class.
I would implement a TaskDTO, use Hibernate's session to load the User by its id, then build a Task from the User and the other fields from TaskDTO, then save the Task like you do.
Regarding your other questions
2 - With a create or updatevalue for hibernate.hbm2ddl.auto, Hibernate can generate or update the tables when you start the application.
5 - You could have this interface
#GetMapping("/user/{id}")
public List<TaskDTO> getTasks(#PathVariable Long id)
Then I think you can't escape coding a criteria query of some sort.
6 - This is done with configuring the relation with cascade = CascadeType.ALL
The hibernate builds table and foreign keys automatically.Complex queries we can write in repo/controller in hibernate syntax.
With Crud repository we can delete , create update and read data easily.
for example we have student to course one to many relation.
#OneToMany
#JoinColumn(name = "studentId", referencedColumnName = "studentId", insertable = false, updatable = false)
private List<StudentCourses> studentCourses;
in StudentController I will write
#CrossOrigin(origins = "http://localhost:8090")
#RequestMapping(value = "/registerStudentCourse", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces =
MediaType.APPLICATION_JSON_VALUE )
public StudentCourses registerStudentCourse(#RequestBody StudentCourses studentCourse) {
if (studentCourse != null) {
studentCourse.setLastupdated(new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date()));
studentCourse = studentCourseRepository.save(studentCourse);
}
return studentCourse;
}
#CrossOrigin(origins = "http://localhost:8090")
#RequestMapping(value = "/findStudentCourses", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE, produces =
MediaType.APPLICATION_JSON_VALUE )
public List<StudentCourses> findStudentCourses(#RequestBody String studentId) {
List<StudentCourses> courses = new ArrayList<>();
JSONObject requestedJSONObject;
try {
requestedJSONObject = new JSONObject(studentId);
String student = requestedJSONObject.getString("studentId");
courses =
studentCourseRepository.findCoursesByStudent(Long.parseLong(student));
} catch (JSONException e) {
// TODO Auto-generated catch block
}
return courses;
}

Alternative to save in JPA/Hibernate with non autogenerated ID

I'm trying to insert a value into a database with type String in ID.
#Entity
#Table(name = "xpto_version_map")
public class XptoVersionMap implements Serializable {
#Id
#Column(name = "uniq_name", unique = true, nullable = false)
private String uniq_name;
...
When trying to save a new XptoVersionMap() like:
XptoVersionMap xptoVersionMap = new XptoVersionMap();
xptoVersionMap.setUniqName("XPTO-1");
xptoVersionMap.setValue("value2");
xptoVersionMapRepository.save(xptoVersionMap);
Will throw:
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find xxx.api.database.entity.XptoVersionMap with id XPTO-1; nested exception is javax.persistence.EntityNotFoundException: Unable to find xxx.api.database.entity.XptoVersionMap with id XPTO-1.
I've tried different solutions, but unless I can make a native query to insert the value, I can't have a way to tell Hibernate that I want to just check if the #Id (uniq_name) exists, if not insert the new value and not to throw an exception.
That's how Hibernate works by default. It assigns an automated generated ID when you do save method and ID is not set. If there is an ID it tries to update it (what happens in your case).
save Persists an entity. Will assign an identifier if one doesn't
exist. If one does, it's essentially doing an update. Returns the
generated ID of the entity.
You can do a workaround for example by using persist and #PrePersist
#PrePersist
void generateId() {
if (uniq_name == null) {
uniq_name = GENERATE_SOME_UNIQUE_ID_SO_IT_DOESN'T_BREAK();
}
}
And then use xptoVersionMapRepository.persist(xptoVersionMap);
Or you can write your own generator with something like that:
#GenericGenerator(name = "my_generator", strategy = "package.CustomGenerator")
#GeneratedValue(generator = "my_generator")
And then create a class CustomGenerator that implements IdentifierGenerator and create the required methods.

Caused by: org.hibernate.TransientObjectException: The given object has a null identifier: com.models.User on hibernate update

I am trying to make an update in the database row. I am having this exception
Caused by: org.hibernate.TransientObjectException: The given object has a null identifier: com.models.User
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.getUpdateId(DefaultSaveOrUpdateEventListener.java:270)
this is my controller code for the submit action from the jsp file
// create new user object
User user = new User();
user.setName(name);
user.setEmail(email);
user.setActive(false);
_userDao.update(user);
this is my dao that defines the update with hibernate session factory utility
public void update(User user) {
getSession().update(user);
}
//EDITTED: this is my mapping for user entity class
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "userId")
private Integer id;
#Column(nullable = false)
private String name;
#Column(unique = false, nullable = false)
private String email;
#Column(nullable = true)
private boolean active;
I am not able to update the user record where the email address is equal to the one entered in the jsp input form. Kindly assist, this is my first attempt in updating a field with hibernate sessionfactory.
The problem is that you are using update method on not existing entity. If you want to save newly created entity you have to use save or saveOrUpdate.
update method works only if entity already exists in DB.
I had the slimier situation at my work. As #ByeBye said the problem was trying to update an entity which was not persisted before. See following example
SalesOrder salesOrder = consignment.getSalesOrder();
if (salesOrder != null) {
// some code
}
else{
//
salesOrder = new SalesOrder();
consignment.setSalesOrder(salesOrder);
}
salesOrderRepository.update(salesOrder); // hibernate update
return salesOrder;
}
Here when the execution of code comes to else part. It try to create a new sales order object (where the object id is null ) and try to update it. And this cause the mentioned error.
So my fix was simply changing the update to saveOrUpdate . see below
.......
salesOrder = new SalesOrder();
consignment.setSalesOrder(salesOrder);
}
salesOrderRepository.saveOrUpdate(salesOrder);
return salesOrder;
}
So then it commands hibernate to first persist the non-existing objects and then do all the updates upon that (if required ).
Hope this scenario will help to all

Hibernate #SQLDelete sql not adding schema

I am trying to use the #SQLDelete annotation of Hibernate to make soft deletion. It works well when the DB schema is static, i.e: passing it in the SQL.
Unfortunately, it seems the SQL is passed as is to EntityPersisters (cf EntityClass's method CustomSQL createCustomSQL(AnnotationInstance customSqlAnnotation) so I can't find a way to pass the schema name dynamically like in Native SQL queries using {h-schema}
Did anyone find a good workaround for this issue (I am using Hibernate 4.3.5)?
Edit: Unless there is a real solution, I ended up modifying the code source of org.hibernate.persister.entity.AbstractEntityPersister by replacing the schema placeholder when setting the custom SQL queries in method doLateInit.
Edit2: I have created an issue for this behaviour in Hibernate JIRA. I will create a pull request later today and I wish the Hibernate Team will accept it
Soft deletes using Hibernate annotations.
As linked author stated below:
I am currently working on a Seam application that has a need for soft deletes in the database. To the right you can see a snippet of my database diagram which contains a CUSTOMER and APP_USER table. This is just a straight forward one to many relationship but the important thing to note though is the “DELETED” field in each table. This is the field that will be used to track the soft delete. If the field contains a ‘1’ the record has been deleted and if it contains a ‘0’ the record hasn’t been deleted.
Before ORMs like Hibernate I would have had to track and set this flag myself using SQL. It wouldn’t be super hard to do but who wants to write a bunch of boilerplate code just to keep track of whether or not a record has been deleted. This is where Hibernate and annotations comes to the rescue.
Below are the 2 Entity classes that were generated by Hibernate using seamgen. I have omitted parts of the code for clarity.
Customer.java
//Package name...
//Imports...
#Entity
#Table(name = "CUSTOMER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
#SQLDelete(sql="UPDATE customer SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
#Where(clause="deleted <> '1'")
public class Customer implements java.io.Serializable {
private long id;
private Billing billing;
private String name;
private String address;
private String zipCode;
private String city;
private String state;
private String notes;
private char enabled;
private char deleted;
private Set appUsers = new HashSet(0);
// Constructors...
// Getters and Setters...
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "customer")
// Filter added to retrieve only records that have not been soft deleted.
#Where(clause = "deleted <> '1'")
public Set getAppUsers() {
return this.appUsers;
}
public void setAppUsers(Set appUsers) {
this.appUsers = appUsers;
}
}
AppUser.java
//Package name...
//Imports...
#Entity
#Table(name = "APP_USER")
//Override the default Hibernation delete and set the deleted flag rather than deleting the record from the db.
#SQLDelete(sql="UPDATE app_user SET deleted = '1' WHERE id = ?")
//Filter added to retrieve only records that have not been soft deleted.
#Where(clause="deleted <> '1'")
public class AppUser implements java.io.Serializable {
private long id;
private Customer customer;
private AppRole appRole;
private char enabled;
private String username;
private String appPassword;
private Date expirationDate;
private String firstName;
private String lastName;
private String email;
private String phone;
private String fax;
private char deleted;
private Set persons = new HashSet(0);
// Constructors...
// Getters and Setters...
}
The following 2 steps is all that I had to do to implement the soft delete.
Added the #SQLDelete annotation which overrides the default
Hibernate delete for that entity.
Added the #Where annotation to filter the queries and only return
records that haven’t been soft deleted. Notice also that in the
CUSTOMER class I added an #Where to the appUsers collection. This is
needed to fetch only the appUsers for that Customer that have not
been soft deleted.
Viola! Now anytime you delete those entities it will set the “DELETED” field to ‘1’ and when you query those entities it will only return records that contain a ‘0’ in the “DELETED” field.
Hard to believe but that is all there is to implementing soft deletes using Hibernate annotations.
Note:
also note that instead of using the #Where(clause="deleted ‘1’") statements you can use hibernate filter (http://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-filters) to globally filter-out all ‘deleted’ entities. I found that defining 2 entity managers (‘normal’ one that filter deleted items, and one that doesn’t, for the rare cases…) is usually quite convenient.
Using EntityPersister
You can create a DeleteEventListener such as:
public class SoftDeleteEventListener extends DefaultDeleteEventListener {
/**
*
*/
private static final long serialVersionUID = 1L;
#Override
public void onDelete(DeleteEvent event, Set arg1) throws HibernateException {
Object o = event.getObject();
if (o instanceof SoftDeletable) {
((SoftDeletable)o).setStatusId(1);
EntityPersister persister = event.getSession().getEntityPersister( event.getEntityName(), o);
EntityEntry entityEntry = event.getSession().getPersistenceContext().getEntry(o);
cascadeBeforeDelete(event.getSession(), persister, o, entityEntry, arg1);
cascadeAfterDelete(event.getSession(), persister, o, arg1);
} else {
super.onDelete(event, arg1);
}
}
}
hook it into your persistence.xml like this
<property name = "hibernate.ejb.event.delete" value = "org.something.SoftDeleteEventListener"/>
Also, don't forget to update your cascades in your annotations.
Resource Link:
Hibernate: Overwrite sql-delete with inheritace
Custom SQL for CRUD operations
Custom SQL for create, update and delete
Use like this
#SQLDelete(sql = "UPDATE {h-schema}LEAVE SET STATUS = 'DELETED' WHERE id = ?", check = ResultCheckStyle.COUNT)
I think there are 2 way
First is to add:
app.datasource.schema=<schema_name>
to your application.properties.
The second is to use the schema in annotation to your table model

One-to-one mapping hibernate and not null

I'm new to hibernate and quite new to MySQL too.
I have the following two tables:
CREATE TABLE storeman.user (
id INT NOT NULL AUTO_INCREMENT,
email VARCHAR(80) NOT NULL,
display_name VARCHAR(50),
password CHAR(41),
active BOOLEAN NOT NULL DEFAULT FALSE,
provisional BOOLEAN NOT NULL DEFAULT FALSE,
last_login TIMESTAMP,
PRIMARY KEY (id),
UNIQUE INDEX (email)
);
CREATE TABLE storeman.user_preferences (
id INT NOT NULL AUTO_INCREMENT,
notify_login BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (id),
CONSTRAINT id_foreign FOREIGN KEY (id) REFERENCES user (id) ON DELETE CASCADE
);
In Eclipse, with hibernate tools I have generated the domain code classes. User.java looks like this (siplified):
#Entity
#Table(name = "user", catalog = "storeman", uniqueConstraints = #UniqueConstraint(columnNames = "email"))
public class User implements java.io.Serializable {
private Integer id;
[...]
private UserPreferences userPreferences;
public User() {
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
[...]
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
public UserPreferences getUserPreferences() {
return this.userPreferences;
}
}
My issue is with getUserPreferences: of course, that would return null if creating a new user or reading from the db where the corresponding row in the user_detail table does not exist. This is correct, however it forces me to check if userPreferences is not null before accessing its members. And from a coding point of view it is not so handy. So I changed User.getUserPreferences method like this, to get a default value:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
public UserPreferences getUserPreferences() {
if (this.userPreferences==null)
this.userPreferences = new UserPreferences();
return this.userPreferences;
}
This is working fine, however if I ever would need to re-generate domain code (User.java) with hibernate tools, that change will be lost. So my question is: is there a way (even by modifying mySQL table/relationships) to automatically have userPreferences always set?
There is no way to do this outside of your code (at least not that I can think of), with some configuration or something like that.
One thing you can do is to initialize the relation when you declare it
private UserPreferences userPreferences = new UserPreferences()
but that also won't survive code regeneration. The only other way I can think of is to put this initialization code into some util method so you can maintain it there regardless of regeneration of entity code.
UserUtils.getUserPreferences(User user)
However, this would only work for the code you write, if some framework needs it you will again get null values because it will not use your util method (the first approach is better in this case).
Do bear in mind that, when you initialize this object on a managed entity, the new object will be persisted into the database.
User user = userDAO.getUser(id);
user.getUserPreferences(); // this code initializes the relation (new UserPreference())
After these lines, you will get a row in user_preferences table if cascade is configured in that manner, or you will get an exception complaining about transient entity found in entity you are trying to persist.
All that being said, is it really that hard to check if something is null, especially if by business rules it is allowed to be null?

Categories