[INTRO]
Database : Apache Derby
JPA : EclipseLink
Hey, I was looking for answer but couldn't find any so here it goes. I'm writing simple sudoku app, and the next feature which I would like to add is saving my Sudoku boards in database, and retrieve them when it's needed. Here is UML diagram of my main two classes:
SudokuBoard.uml
The structure of my two entities are as follows :
The SudokuBoard entity:
#Entity
public class SudokuBoard implements Serializable, Cloneable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private static final long serialVersionUID = 1L;
#OneToMany(cascade=CascadeType.PERSIST)
private ArrayList<SudokuField> board;
public ArrayList<SudokuField> board() {
return board;
}
public void setBoard(ArrayList<SudokuField> board) {
this.board= board;
}
public Long etId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
The SudokuField entity:
#Entity
public class SudokuField implements Serializable, Comparable<SudokuField>,
Cloneable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Transient
private static final Logger logger =
LoggerFactory.getLogger(SudokuField.class);
#Transient
private static final long serialVersionUID = 1L;
#Basic(fetch = FetchType.EAGER)
#Column(name = "Value")
private int value;
#ManyToOne
#JoinColumn(name = "board_fk", referencedColumnName = "id")
private SudokuBoard sudokuBoard;
The execution in DAO:
#Override
public void write(SudokuBoard obj, String path) throws
FileNotFoundException, IOException {
entityManager.getTransaction().begin();
entityManager.persist(obj);
entityManager.getTransaction().commit();
}
[PROBLEM]
I wonder if there is any possiblity to not use auto generated key but instead use String as PK in SudokuBoard entity. In my application I implemented binding so I would like to save the same object of SudokuBoard which is changing over time under different names.
Hope I stated my intentions clearly. Thanks for any help and tips how it could be done.
Using String as primary key is straightforward - just declare it as such and drop the #GeneratedValue annotation.
However, changing the primary key (if that's what you mean by 'saving the same object (...) under different names') is not possible. If you try to persist/merge an existing entity under a different primary key, JPA will either raise an exception or treat it as a new entity, resulting in duplicate entries in the database. Simply put, assigning id to an entity is permanent.
All in all, I'd suggest you keep an autogenerated surrogate key and declare another unique String field for the name. Conveying business information using technical fields is rarely a good idea.
Related
I have an entity EncodingResult that references three others. I want to find out how to use the repository's findBy() methods to return an entity based on its foreignKey so that I can, for example, make a GET request passing a Video's foreign key as a parameter and return whether or not there is an EncodingResult containing a Video with the given foreignKey.
How would you go about doing this? I tried reading a bit on EntityGraphs and was rather confused. There also doesn't seem to be a great number of content explaining these parts of the framework.
It would be better if you posted the code for your entities, but from your description, I think you have something like this:
#Entity
public class EncodingResult {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy = "encodingResult")
private List<Video> videos=new ArrayList<Video>();
//...boilerplate
}
#Entity
public class Video {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToOne
EncodingResult encodingResult;
//...boilerplate
}
So you can define findBy methods in your EncodingResultRepository like so.
public interface EncodingResultRepository extends JpaRespository<EncodingResult, Integer> {
public Optional<EncodingResult> findByVideoName(String name);
public Optional<EncodingResult> findByVideoId(Integer id);
}
I found similar questions, but they did not answer my question.
I have two entities with a many-to-one relationship - unidirectional.
But most importantly, the relationship is lazy. Because it is correct to use a lazy connection, everyone knows it.
Code:
#Entity
public class User implements BaseEntity {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private City city;
}
#Entity
public class City implements BaseEntity {
#Id
#Column
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
}
interface BaseEntity {
void setId(Long id);
Long getId();
}
I wrote a method that allows you to search by the transferred fields of the entity.
An example of how this works:
public class Search<T extends BaseEntity> {
public List<T> getByFields(T entity, List<FieldHolder> data) {
// create criteria with passed field name and value by reflection
}
}
class FieldHolder {
private String fieldName;
private Object value;
/**
* "true" - means that the field "value" contains id related object
* "false" - elementary type like: String, Wrapper, Primitive
*/
private boolean isRelationId;
}
The problem is that problems start when you need to search and related objects - by creating related queries.
The following entry is used to send the associated field: "city.id" and the problem is that when I transfer the essence of the related object (City) it is in a proxy and I cannot get id by reflection from City.
My function works perfectly if you specify:
#ManyToOne(fetch = FetchType.EAGER)
private City city;
But it will greatly affect performance, since I have a lot of related objects. Therefore, I want to solve this problem for a lazy load.
I know that this is not an easy task. But perhaps there is some opportunity to somehow get around this problem.
I'm new to JPA. Suppose I have these two entities:
//Imports
#Entity
#Table(name="article", schema = "sch_client")
public class Article implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int price;
private int amount;
//Getters & setters
}
And
#Entity
#Table(name="purchase", schema = "sch_client")
public class Purchase implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany
private List<Article> listArticle;}
I want to have something like a purchase contains many articles.
My question is: is it possible with only #OneToMany in Purchase class that points to Article class to have the desired relationship (a purchase contains many articles). Or to use a #OneToMany annotation I have to add a #ManyToOne on Article class. If so, why is is mandatory to add the #ManyToOne? any explanation please.
Thanks in advance.
First of all, I have write a misleading title, I will change it to make it more accurate:
Old title : In JPA, is it possible to use #OneToMany without using #ManyToOne?
New title : #OneToMany does not create the join table.
As I said, I'm new to JPA, my problem can appear dumb, I could delete the question, but I decided to keep it in case someone someday will face similar situation, it can help!
The join table of Purchase and Article was created every time I executed the code very normally, but I didn't notice!, I was checking the logs of NetBeans
and didn't see the join table, I was misled by those logs, I think that a join table doesn't appear in the logs (I hope that someone can confirm this information and make an edit of this answer).
I have created Purchase and Article in a new schema named: sch_sales. and the join table was created in public schema (PostgreSQL).
So, to make it more correct I added schema to #JoinTable as shown below, like this I will have all my tables in the same schema.
#Entity
#Table(name="purchase", schema = "sch_sales")
public class Purchase implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany
#JoinTable(name="join_purchase_article", schema = "sch_sales", joinColumns = #JoinColumn(name="sales_fk"), inverseJoinColumns = #JoinColumn(name="article_fk"))
private List<Article> listArticle;
}
UPDATE :
I was having a 3rd table created containing the id of Purchase and Article (a join table) which is obviously not correct.
The normal "behavior" is to have an id_purchase column added in Article, in this page I have find how to have such a result.
To have the desired result, I used the code below:
#Entity
#Table(name="purchase", schema = "sch_sales")
public class Purchase implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany
#JoinColumn(name="id_purchase")
private List<Article> listArticle;
}
It seems to me that there is virtually no difference between the below two ways of mapping. Here is an example base on #MapsId javadoc:
// parent entity has simple primary key
#Entity
public class Employee {
#Id long empId;
...
}
// dependent entity uses EmbeddedId for composite key
#Embeddable
public class DependentId {
String name;
long empid; // corresponds to primary key type of Employee
}
#Entity
public class Dependent {
#EmbeddedId DependentId id;
...
#MapsId("empid") // maps the empid attribute of embedded id
#ManyToOne Employee emp;
}
What if I change Dependent's mapping to:
#Entity
public class Dependent {
#EmbeddedId DependentId id;
#ManyToOne
#JoinColumn("empid", insertable=false, updatable=false)
Employee emp;
}
What is the difference of the above two approach?
So, I tested #MapsId for my usage when in the table I have only one foregin key it was no different. But for tables where I have two foregin keys to one table like ...
UserTable, and EmailTable-> #MapsId(owner)UserTable owner, #MapsId(receiver) UserTable receiver i have problems with that. Hibernate throws exceptions. So i have to back to old #JoinColumn way of doing that. That was a one differemce that I met with that adnotations.
I am using combination of both #MapsId and #JoinColumn together to avoid getting extra field getting created in DB for associating the entities. IF I ignore #JoinColumn, an extra field is getting created in DB.
#Entity
public class BookingsModel implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private SlotDateModel slotDateModelObj;
#JsonProperty
String slotnumber;
#MapsId("memberid")
#JsonBackReference
#ManyToOne
#JoinColumn(name="memberid",referencedColumnName = "memberid")
#NotNull
MemberModel memberModel;
.
.
.
}
#Entity
public class MemberModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#JsonProperty
#Id
String memberid;
#JsonProperty
String name;
#JsonIgnore
String phoneno;
#JsonManagedReference
#OneToMany
Set<BookingsModel> bookings;
.
.
.
}
#Embeddable
public class SlotDateModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
String memberid;
String slotdate;
.
.
.
}
Tables generated with #JoinColumn
Table generated when #JoinColumn is commented Can notice that the extra field "member_model_memberid" is getting added.
I'm trying to map two objects to each other using a ManyToMany association, but for some reason when I use the mappedBy property, hibernate seems to be getting confused about exactly what I am mapping. The only odd thing about my mapping here is that the association is not done on a primary key field in one of the entries (the field is unique though).
The tables are:
Sequence (
id NUMBER,
reference VARCHAR,
)
Project (
id NUMBER
)
Sequence_Project (
proj_id number references Project(id),
reference varchar references Sequence(reference)
)
The objects look like (annotations are on the getter, put them on fields to condense a bit):
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany(mappedBy="sequences")
private List<Project> projects;
}
And the owning side:
class Project {
#Id
private int id;
#ManyToMany
#JoinTable(name="sequence_project",
joinColumns=#JoinColumn(name="id"),
inverseJoinColumns=#JoinColumn(name="reference",
referencedColumnName="reference"))
private List<Sequence> sequences;
}
This fails with a MappingException:
property-ref [_test_local_entities_Project_sequences] not found on entity [test.local.entities.Project]
It seems to weirdly prepend the fully qualified class name, divided by underscores. How can I avoid this from happening?
EDIT:
I played around with this a bit more. Changing the name of the mappedBy property throws a different exception, namely:
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: test.local.entities.Project.sequences
So the annotation is processing correctly, but somehow the property reference isn't correctly added to Hibernate's internal configuration.
I have done the same scenario proposed by your question. And, as expected, i get the same exception. Just as complementary task, i have done the same scenario but with one-to-many many-to-one by using a non-primary key as joined column such as reference. I get now
SecondaryTable JoinColumn cannot reference a non primary key
Well, can it be a bug ??? Well, yes (and your workaround works fine (+1)). If you want to use a non-primary key as primary key, you must make sure it is unique. Maybe it explains why Hibernate does not allow to use non-primary key as primary key (Unaware users can get unexpected behaviors).
If you want to use the same mapping, You can split your #ManyToMany relationship into #OneToMany-ManyToOne By using encapsulation, you do not need to worry about your joined class
Project
#Entity
public class Project implements Serializable {
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy="project")
private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();
#Transient
private List<Sequence> sequenceList = null;
// getters and setters
public void addSequence(Sequence sequence) {
projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(id, sequence.getReference())));
}
public List<Sequence> getSequenceList() {
if(sequenceList != null)
return sequenceList;
sequenceList = new ArrayList<Sequence>();
for (ProjectSequence projectSequence : projectSequenceList)
sequenceList.add(projectSequence.getSequence());
return sequenceList;
}
}
Sequence
#Entity
public class Sequence implements Serializable {
#Id
private Integer id;
private String reference;
#OneToMany(mappedBy="sequence")
private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();
#Transient
private List<Project> projectList = null;
// getters and setters
public void addProject(Project project) {
projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(project.getId(), reference)));
}
public List<Project> getProjectList() {
if(projectList != null)
return projectList;
projectList = new ArrayList<Project>();
for (ProjectSequence projectSequence : projectSequenceList)
projectList.add(projectSequence.getProject());
return projectList;
}
}
ProjectSequence
#Entity
public class ProjectSequence {
#EmbeddedId
private ProjectSequenceId projectSequenceId;
#ManyToOne
#JoinColumn(name="ID", insertable=false, updatable=false)
private Project project;
#ManyToOne
#JoinColumn(name="REFERENCE", referencedColumnName="REFERENCE", insertable=false, updatable=false)
private Sequence sequence;
public ProjectSequence() {}
public ProjectSequence(ProjectSequenceId projectSequenceId) {
this.projectSequenceId = projectSequenceId;
}
// getters and setters
#Embeddable
public static class ProjectSequenceId implements Serializable {
#Column(name="ID", updatable=false)
private Integer projectId;
#Column(name="REFERENCE", updatable=false)
private String reference;
public ProjectSequenceId() {}
public ProjectSequenceId(Integer projectId, String reference) {
this.projectId = projectId;
this.reference = reference;
}
#Override
public boolean equals(Object o) {
if (!(o instanceof ProjectSequenceId))
return false;
final ProjectSequenceId other = (ProjectSequenceId) o;
return new EqualsBuilder().append(getProjectId(), other.getProjectId())
.append(getReference(), other.getReference())
.isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder().append(getProjectId())
.append(getReference())
.hashCode();
}
}
}
I finally figured it out, more or less. I think this is basically a hibernate bug.
edit: I tried to fix it by changing the owning side of the association:
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany
#JoinTable(name="sequence_project",
inverseJoinColumns=#JoinColumn(name="id"),
joinColumns=#JoinColumn(name="reference",
referencedColumnName="reference"))
private List<Project> projects;
}
class Project {
#Id
private int id;
#ManyToMany(mappedBy="projects")
private List<Sequence> sequences;
}
This worked but caused problems elsewhere (see comment). So I gave up and modeled the association as an entity with many-to-one associations in Sequence and Project. I think this is at the very least a documentation/fault handling bug (the exception isn't very pertinent, and the failure mode is just wrong) and will try to report it to the Hibernate devs.
IMHO what you are trying to achieve is not possible with JPA/Hibernate annotations. Unfortunately, the APIDoc of JoinTable is a bit unclear here, but all the examples I found use primary keys when mapping join tables.
We had the same issue like you in a project where we also could not change the legacy database schema. The only viable option there was to dump Hibernate and use MyBatis (http://www.mybatis.org) where you have the full flexibility of native SQL to express more complex join conditions.
I run into this problem a dozen times now and the only workaround i found is doing the configuration of the #JoinTable twice with swapped columns on the other side of the relation:
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany
#JoinTable(
name = "sequence_project",
joinColumns = #JoinColumn(name="reference", referencedColumnName="reference"),
inverseJoinColumns = #JoinColumn(name="id")
)
private List<Project> projects;
}
class Project {
#Id
private int id;
#ManyToMany
#JoinTable(
name = "sequence_project",
joinColumns = #JoinColumn(name="id"),
inverseJoinColumns = #JoinColumn(name="reference", referencedColumnName="reference")
)
private List<Sequence> sequences;
}
I did not yet tried it with a column different from the primary key.