I am new to Spring and JPA and I am trying to write a job in Spring which runs every 3 hours and retrieve the records from Oracle Database.
I would like to only read the new/updated content from the past 3 hours (ideally from the last job run).
I have seen few examples in https://spring.io/blog/2011/02/10/getting-started-with-spring-data-jpa/ where we can create queries and retrieve the data based on our requirements, but in my current use case, I am not using queries instead using the java classes with the annotations and using Join columns between different tables. There are chances that only one of the sub table is updated or all the tables are updated with new content. I need to get the results if at least one of the table is updated/inserted with new content.
Campus is the main table and retrieves the data from Group and Region, I need to fetch the data if any new data is updated in Campus table or even any group/region is added/modified.
I am using JDK7 as of now.
Is there a way to accomplish the above requirement?
Below are the sample Java classes for reference.
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_CAMPUS")
public class Campus implements Auditable {
#Id
#Column(name = "ID)
#SequenceGenerator(name = "SIMPLE_ID", sequenceName = "SIMPLE_ID")
#GeneratedValue(generator = "SIMPLE_ID", strategy = GenerationType.AUTO)
private Long id;
#Column(name = "CAMPUS_NAME")
private String campusName;
#Column(name = "CAMPUS_ID", nullable = false)
private Long campusId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "GROUP_ID")
private GroupType groupType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "REGION_ID")
private Region region;
....
...
}
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_GROUP_TYPE")
public class GroupType implements Auditable {
#Id
#Column(name = "GROUP_TYPE_ID")
#SequenceGenerator(name = "GROUP_TYPE_SEQUENCE", sequenceName = "GROUP_TYPE_ID")
#GeneratedValue(generator = "GROUP_TYPE_SEQUENCE", strategy = GenerationType.AUTO)
protected Long id;
#Column(name = "GROUP_TYPE_NAME", nullable = false)
protected String groupTypeName;
....
...
}
#Entity
#EntityListeners(AuditListener.class)
#Table(name = "TABLE_REGION")
public class Region implements Auditable {
#Id
#Column(name = "region_id")
#SequenceGenerator(name = "REGION_ID", sequenceName = "REGION_ID")
#GeneratedValue(generator = "REGION_ID", strategy = GenerationType.AUTO)
private Long id;
#Column(name = "REGION_NAME", nullable = false)
private String name;
...
..
}
Any help is Appreciated.
I have 2 tables (User and Feed) linked by a foreign key. Following that, I am using Spring Boot and Hibernate to make a query to just print out all the values in Feed table. But when it comes to the foreign key and its value, my Entity seems to be going wrong where it creates a new column on the fly when I already have a column for the foreign key.
Can I please know what I am doing wrong? New to this JPA setup. Confused as to whether I should even create my schema first or let JPA just handle things according to my Entity setups. Clearly I am missing something vital this but just can't place a finger on it. Please assist.
Question is how do I map to the foreign key? As in map Feed table's foreign key 'owner_name' to User table's 'username' on the Entity?
Table structures
Entities
User Entity
#Entity
public class User {
#Id
#Column(name = "username")
public String username;
#Column(name = "email")
public String email;
#Column(name = "password")
public String password;
#Basic
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at")
private Date createdAt;
#Column(name = "online_status")
private long onlineStatus;
#Column(name = "account_status")
private long accountStatus;
//updated this based on Gopi's suggestion
#OneToMany(mappedBy = "ownerName")
private List<Feed> feeds;
}
Feed Entity
#Entity
public class Feed {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "feed_id")
private long feedId;
#Column(name = "title")
private String title;
#Basic
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "created_at")
private Date createdAt;
#Basic
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "updated_at")
private Date updatedAt;
#ManyToOne
#JoinColumn(name = "username")
// #Column(name = "owner_name")
private User ownerName;
}
My query to just get all the Feed data where I do get all data less the value for foreign key which comes out as null. (expecting to get the foreign key value instead).
My controller is calling this method where I get all results less the foreign key value.
public interface FeedRepository extends JpaRepository<Feed, Long> {
#Query("select f from Feed as f order by f.createdAt desc")
List<Feed> getCurrentFeeds();
}
Test values inside the tables.
User table data
Feed table data
If I run my current code, I end up with an additional column on the fly as follows which I do not want as mentioned above.
Hi this is not surprising, you have specified as a name attribute to the #JoinColumn the column that is actualy referenced. You need to specify the foreign key column in the Feed table which is the "owner_name". The correct complete definition of the #JoinColumn would be:
#JoinColumn(name="owner_name",referencedcolumn = "username")
private User ownerName;
Where you don't actualy need to define the referencedcolumn, but I have defined it for completion so that you understand what is what.
I created a simple spring mvc web app with hibernate and mysql (Everything worked well). Then, I changed mysql to oracle. After first run of the application, (connected to oracle database) tables, corresponding to the entity models defined in my application, were created. I added one country and one user to COUNTRY and MYAPPUSER tables from SQL DEVELOPER. Now, I am trying to get a user from MYAPPUSER table but I am getting null instead of user, which exists.
This is User entity:
#Entity
#Table(name = "myAppUser")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "id_Sequence")
#SequenceGenerator(name = "id_Sequence", sequenceName = "ID_SEQ")
#Column(name = "Id", updatable = false, nullable = false)
private int id;
#Column(name = "FirstName")
private String firstName;
#Column(name = "LastName")
private String lastName;
#Column(name = "Username")
private String username;
#Column(name = "Password")
private String password;
#OneToMany(mappedBy = "user")
private List<UserSubject> userSubjects;
#ManyToOne
#JoinColumn(name = "country_id", nullable = false)
private Country country;
#OneToMany(mappedBy = "user")
private List<Test> testList;
//and getters and setters
}
This is code while debugging:
In sql developer:
this is generated sql by hibernate:
I am interested in why user is null after selecting data and why question marks are in place of user1 and 123 in generated sql?
I think this effect is specific to oracle, because I had not such problem with MySQL database.
P.S Exceptions aren't thrown....
From the comments the solution was to commit in SQL Developer.
I am using Hibernate(3.0) + Oracle DB (10g) for my application.
My domain object are like PluginConfig.java
#Entity
#Table(name = "plugin_config" , schema = "test")
#org.hibernate.annotations.Cache(usage =org.hibernate.annotations.CacheConcurrencyStrategy.READ_WRITE)
public class Config {
private static final long serialVersionUID = -1959019321092627830L;
/** This object's id */
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected long id;
#OneToOne
#JoinColumn(name = "plugin_id")
private Plugin plugin;
#Column(name = "config_name")
#NaturalId(mutable = true)
private String name;
#Column(name = "config_desc")
private String description;
another domain object Plugin.java
#Entity
#Table(name = "plugin", schema = "test")
#org.hibernate.annotations.Cache(usage = org.hibernate.annotations.CacheConcurrencyStrategy.READ_WRITE)
public class Plugin implements Serializable {
private static final long serialVersionUID = 5694761325202724778L;
/** This object's id */
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected long id;
#Column(name = "plugin_name")
#NaturalId
private String pluginName;
#OneToOne
#JoinColumn(name = "plugin_config_id")
private PluginConfig pluginConfig;
#Column(name = "plugin_desc")
private String description;
Whenever i try to load Plugin via my database service method(using #Transactional annotation and hence it loads all its children as well) i get the following error
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [PluginConfig#53]
There is actually no row in plugin_config table with id = 53.
And also no plugin table entry has plugin_config_id=53.
So from where is hibernate picking these values ?
I noticed one thing here, the value "53" is actually the row number from the plugin_config table which should have been reffered.
See the below image:
This is the plugin config that my query should be looking for. But it tries to search it with identifier #53(Row no:) instead of the #95(value of the primary key "id").
Where can i be going wrong with this ?
I don't know if this is teh best solution, but you can use #NotFound annotation with IGNORE command to let Hibernate discard exception adn set pluginConfig to null.
#OneToOne
#JoinColumn(name = "plugin_config_id")
#NotFound(action = NotFoundAction.IGNORE)
private PluginConfig pluginConfig;
QUESTIONS:
Does anyone know how to merge without having EntityManager trying to re-insert the foreign entity?
SCENARIO:
Just to set up a scenario that closely matches my case: I have two entities
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
#Entity
#Table(name = "friendshiptype", catalog = "friends")
public class FriendshipType implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "username")
private Login login;
#Column(name = "type", unique = true, length = 32)
private String type;
...//other fields go here
}
Both the Login entity and the FriendshipType entity are persisted to the database separately. Then, later, I need to merge a Login row with a FriendshipType row. When I call entityManager.merge(friendship), it tries to insert a new Login which of course results in the following error
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'myUserName1350319637687' for key 'username'
Error Code: 1062
Call: INSERT INTO friends.login (password, username) VALUES (?, ?)
My question, again, is how do I merge two objects without having enityManager trying to reinsert the foreign object?
Here is how I solve the problem. I finally figure the reason the merge is not resolving is because the login.id is auto generated by JPA. So since I really don't need an auto-generated id field, I remove it from the schema and use username as the #id field:
#Entity
#Table(name = "login", catalog = "friends", uniqueConstraints =
#UniqueConstraint(columnNames = "username"))
public class Login implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
#Column(name = "password", nullable = false, length = 250)
private String password;
}
Another solution that occurred to me, which I didn't implement but may help someone else, should they need to have an auto-generated id field.
Instead of creating an instance of Login for the merger, get the instance from the database. What I mean is, instead of
Login login = new Login(); login.setUsername(username); login.setPassword(password);
Do rather
Login login = loginDao.getByUsername(username);
That way, a new id field is not generated making the entity seem different.
Thanks and up-votes to everyone for helping, especially to #mijer for being so patient.
You can make your #JoinColumn non updatable:
#JoinColumn(name = "login_id", updatable = false) // or
#JoinColumn(name = "username", referencedColumnName = "username", updatable= false)
Or try to refresh / fetch your Login entity again before merging the FriendshipType:
// either this
entityManager.refresh(friendship.getLogin());
// or this
final Login login = entityManager
.getReference(Login.class, friendship.getLogin().getId());
friendship.setLogin(login);
// and then
entityManager.merge(friendship);
But, as other suggested I belive that FriendshipType would be better represented by a #ManyToOne relationship or maybe by a Embeddable or ElementCollection
Update
Yet another option is to change the owning side:
public class Login implements java.io.Serializable {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "friendshiptype_id")
private FriendshipType friendshipType;
// Other stuff
}
public class FriendshipType implements java.io.Serializable {
#OneToOne(fetch=FetchType.LAZY, mappedBy="friendshipType")
private Login login;
// Other stuff
}
This will affect your data model (login table will have a friendshiptype_id column instead of the other way around), but will prevent the errors that you are getting, since relationships are always maintained by the owning side.
Have you tried cascade=MERGE? I.e.
#OneToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
UPDATE
Another possible option is to use #ManyToOne (it's save as the association is unique)
#ManyToOne(fetch = FetchType.LAZY, cascade=CascadeType.MERGE)
#JoinColumn(name = "username")
private Login login;
You can do it with your original #Id setup. i.e.
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Integer id;
You can, but you don't need to change to:
#Id
#Column(name = "username", unique = true, nullable = false, length = 50)
private String username;
The trick is you must start by loading from the DB, via em.find(...) or em.createQuery(...). Then the id is guaranteed to be populated with the right value from the DB.
Then you can detach the entity by ending a transaction (for a transaction-scoped entity manager in a session bean), or by calling em.detach(ent) or em.clear(), or by serialising the entity and passing it over the network.
Then you can update the entity, all the while, keeping the original id value.
Then you can call em.merge(ent) and you will still have the correct id. However, I believe the entity must already pre-exist in the persistent context of the entity manager at this instant, otherwise it will think that you have a new entity (with manually populated id), and try to INSERT on transaction flush/commit.
So the second trick is to ensure the entity is loaded at the point of the merge (via em.find(...) or em.query(...) again, if you have a new persistent context and not the original).
:-)