Entity mapping is creating unwanted column on the fly - java

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.

Related

JPA: How to handle versioned entities?

I have a versioning on an entity as part of its primary key. The versioning is done via a timestamp of the last modification:
#Entity
#Table(name = "USERS")
#IdClass(CompositeKey.class)
public class User {
#Column(nullable = false)
private String name;
#Id
#Column(name = "ID", nullable = false)
private UUID id;
#Id
#Column(name = "LAST_MODIFIED", nullable = false)
private LocalDateTime lastModified;
// Constructors, Getters, Setters, ...
}
/**
* This class is needed for using the composite key.
*/
public class CompositeKey {
private UUID id;
private LocalDateTime lastModified;
}
The UUID is translated automatically into a String for the database and back for the model. The same goes for the LocalDateTime. It gets automatically translated to a Timestamp and back.
A key requirement of my application is: The data may never update or be deleted, therefore any update will result in a new entry with a younger lastModified. This requirement is satisfied with the above code and works fine until this point.
Now comes the problematic part: I want another object to reference on a User. Due to versioning, that would include the lastModified field, because it is part of the primary key. This yields a problem, because the reference might obsolete pretty fast.
A way to go might be depending on the id of the User. But if I try this, JPA tells me, that I like to access a field, which is not an Entity:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToOne(optional = false)
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
#Column(nullable = false)
private boolean married;
// Constructors, Getter, Setter, ...
}
What would be the proper way of solving my dilemma?
Edit
I got a suggestion by JimmyB which I tried and failed too. I added the failing code here:
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#OneToMany
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private List<User> users;
#Column(nullable = false)
private boolean married;
public User getUser() {
return users.stream().reduce((a, b) -> {
if (a.getLastModified().isAfter(b.getLastModified())) {
return a;
}
return b;
}).orElseThrow(() -> new IllegalStateException("User detail is detached from a User."));
}
// Constructors, Getter, Setter, ...
}
What you seem to require seems to be on the lines of a history table, to keep track of the changes. See https://wiki.eclipse.org/EclipseLink/Examples/JPA/History on how EclipseLink can handle this for you while using normal/traditional JPA mappings and usage.
What you have here is a logical 1:1 relationship which, due to versioning, becomes a technical 1:n relationship.
You have basically three options:
Clean JPA way: Declare an 'inverse' #ManyToOne relationship from user to the "other object" and make sure you always handle it whenever a new User record is created.
'Hack-ish' way: Declare a #OneToMany relationship in the "other object" and force it to use a specific set of columns for the join using #JoinColumn. The problem with this is that JPA always expects unique reference over the join columns so that reading the UserDetail plus referenced User records should work, whereas writing UserDetail should not cascade onto User to avoid unwanted/undocumented effects.
Just store the user's UUID in the "other object" and resolve the reference yourself whenever you need it.
The added code in your question is wrong:
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private UUID userId;
More correct, albeit not with the result you want, would be
#JoinColumn(name = "USER_ID", referencedColumnName = "ID")
private User user;
This won't work though, because, as I said above, you may have more than one user record per UserDetail, so you'd need a #OneToMany relationship here, represented by a Collection<User>.
Another 'clean' solution is to introduce an artificial entity with a 1:1 cardinality w.r.t. to the logical User to which you can refer, like
#Entity
public class UserId {
#Id
private UUID id;
#OneToMany(mappedBy="userId")
private List<User> users;
#OneToOne(mappedBy="userId")
private UserDetail detail;
}
#Entity
public class User {
#Id
private Long _id;
#ManyToOne
private UserId userId;
}
#Entity
public class UserDetail {
#OneToOne
private UserId userId;
}
This way, you can somewhat easily navigate from users to details and back.
I came to a solution, that is not really satisfying, but works. I created a UUID field userId, which is not bound to an Entity and made sure, it is set only in the constructor.
#Entity
#Table(name = "USER_DETAILS")
public class UserDetail {
#Id
#Column(nullable = false)
private UUID id;
#Column(nullable = false)
// no setter for this field
private UUID userId;
#Column(nullable = false)
private boolean married;
public UserDetail(User user, boolean isMarried) {
this.id = UUID.randomUUID();
this.userId = user.getId();
this.married = isMarried;
}
// Constructors, Getters, Setters, ...
}
I dislike the fact, that I cannot rely on the database, to synchronize the userId, but as long as I stick to the no setter policy, it should work pretty well.

Hibernate OneToMany and ManyToOne error in mapping

I have an entity called User with these fields :
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "last_name")
private String lastName;
#OneToMany(mappedBy="userId")
private List<Survey> survey= new ArrayList<>();
And the Survey entity which has :
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "SURVEY_ID")
private Long Id;
#ManyToOne
#JoinColumn(name = "user_id",referencedColumnName="user_id")
private User userId;
.....
I want one User to be have many Surveys, and each survey is related to one user.
But there is something wrong with the way I've mapped it, cuz as JSON file, when I access allUsers I get this :
[{"id":1,"name":"User","lastName":"user","email":"user#user.com","surveyData":[{"userId":{"id":1,"name":"User","lastName":"user","email":"user#user.com","surveyData": ,...... and repeats itself
So instead of getting as list the values of the survey data, I get the values of the Users information ?
Can someone help me with this ?
Your mapping is correct.
Just use #JsonManagedReference in your User class and #JsonBackReference in your Survey Class. #JsonManagedReference is the forward part of reference – the one that gets serialized normally. #JsonBackReference is the back part of reference – it will be omitted from serialization.
In the User Class:
#OneToMany(mappedBy="userId")
#JsonManagedReference
private List<Survey> survey;
In the Survey Class:
#ManyToOne
#JoinColumn(name = "user_id",referencedColumnName="user_id")
#JsonBackReference
private User userId;
I have 2 remarks:
If the surveys are not ordered, you can consider to use a Set instead of a List.
I would also recommend to rename the class variable userId in the Survey class to user, since it is a User object and no identifier.

Named query to find all entities based on list (instance variable) containing certain values

is it possible using named queries to find all entities based on list containing a certain value.
I have an entity called User
public class User implements Serializable {
private static final long serialVersionUID = -82485348776234092345L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotNull
#Size(min = 5, max = 50)
#Column(name = "email")
private String email;
#NotNull
#Size(min = 5, max = 50)
#Column(name = "password")
private String password;
#Column(name = "creationDate")
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "user", cascade = CascadeType.ALL)
private List<Phone> phoneNumbers;
/* Getters and Setters */
}
I am going to simplify the Phone entity for keeping this post clean:
public class Phone implements Serializable {
private static final long serialVersionUID = -34672347534985L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotNull
#Column(name = "number")
private String number;
#Column(name = "type")
#Enumerated(EnumType.STRING)
private EPhoneType phoneType;
#Column(name = "creationDate")
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
#ManyToOne
private User user;
/* Getters and Setters */
}
Now I need to find all the users that have the phone number, lets say: 289647.... and Phone.type = 'MOBILE'
Phone type is an enum.
I am not sure how to acheive this using a named query. With regular native query I can get this done using a JOIN on the tables. Has anyone done something like this with a NamedQuery
First of all, you are probably confusing "named queries" with "JPQL" queries.
A JPA query can be either "native" and "JPQL" - the difference is the query language (SQL or OQL-like; the first is record-oriented, the second is object-oriented).
Both types of queries can be dynamic (built in runtime) or static (aka "named", defined as a static part of the persistence context, just like the entities). The latter can be optimized by creating and storing prepared queries to represent them.
Joining in OQL-like languages (HQL, JPQL) is done in a similar way as in SQL, but they don't represent joining relations (tables) but rather - "object attributes". When joining on an attribute, you don't define any criteria, because these are already part of the entity's definition. On the other hand, joining will create a row per association between entities, so you probably want to use distinct clause (to avoid having User repeated as many times, as a matching Phone appears on the phoneNumbers list).
Probably what you want is:
select u from User u left join u.phoneNumbers n where n.type = :type and n.number = :number
The query above can be used both as named or unnamed variety, to run it you have to provide named parameters type and number: an enum and a string.

Many to one Hibernate mapping

So, here'e the situation:
Table ComputerInventory with {computerInventoryID (Primary key), TagID(unique), Name etc}
Table reviewStatus with {reviewStatusID(Primary key), computerInventoryID (ForeignKey), status }
I've written the hibernate Entity for ReviewStatus:
public class ReviewStatus implements Serializable {
public enum reviewStatus {
To_Be_Reviewed,
Reviewed
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long reviewStatusId;
#Column(name = "lastModifiedTime")
private Date lastModifiedTime;
#Column(name = "Comments")
private String comments;
#Enumerated(EnumType.STRING)
#Column(name = "status")
//all above params have gettetrs and setters
private reviewStatus status;
//TODO: Mapping to computerinventory
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "computerInventoryId")
//For one ComputerInventoryID, there can be many review Statuses.
private Set<ReviewStatus> reviewStatusSet;
public long getreviewStatusId() {
return reviewStatusId;
}
My Doubts:
For one ComputerInventoryID, there can be many review Statuses, so do I have a
Set<ReviewStatus> reviewStatusSet
where I return the list of entries in reviewstatus? Sorry, but I don't understand how I can write a get/set for returning and setting the reviews status of a bunch of records.
Your reference from ReviewStatus should be to a ComputerInventory, not to its ID. Hibernate lets you abstract out the details of the primary key (the ID), letting you directly reference from one object to another. You should use an #ManyToOne annotation on your private ComputerInventory computerInventory;.

Oracle and HIbernate, db sequence generator issues

I have the following entity (getters and setters ommited)...
#Entity
#Table(name = "TBL_PROJECT_RUN")
public class ProjectRunEntity {
#Id
#Column(name = "ID")
#GeneratedValue(generator = "StakeholdersSequence")
#SequenceGenerator(name = "StakeholdersSequence", sequenceName = "STAKEHOLDERS_UPDATE_SEQ", allocationSize = 1)
private Integer id;
#Column(name = "REPORT_RUN_DATE")
private Date systemRunDate;
#Column(name = "JIRA_PROJECT_NAME")
private String jiraProjectName;
#Column(name = "JIRA_PROJECT_DESC")
private String jiraProjectDescription;
#Column(name = "RUNBY")
private String runBy;
#Column(name = "HEADER_TEXT")
private String headerText;
#Column(name = "FOOTER_TEXT")
private String footerText;
#Column(name = "JIRA_ID")
private String jiraId;
#Column(name = "TO_EMAIL")
private String toEmail;
#Column(name = "CC_EMAIL")
private String ccEmail;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "projectRunEntity")
private List<ProjectRunDetailsEntity> projectRunDetailsEntities;
Then I commit it to the database like this...
final Session session = sessionProvider.get();
session.persist(projectRunEntity);
session.flush();
return projectRunEntity.getId();
The id returned here is not what the actual id in the database is, it is 1 or 2 off. Please note I am sharing one sequence for all ids in all tables in my project so that any given entity has a project-wide unique index. What would cause the id to be incorrect like this?
It turns out that the web ui for oracle express automatically created triggers than insert an id from this sequence before inserting my row object. This meant that the generator in oracle was always 1 behind. To solve this I removed the triggers.
Two possibilities spring to mind:
You say you have a shared sequence. Are you inserting into other tables at the same time? (which could increment the sequence).
Is there a trigger on the database table which inserts a sequence value into the id column?

Categories