JPA JoinTable with two columns referencing to same column in parent table - java

I am creating entity classes from database (Oracle 11g) in a Java SE 7 application using Netbeans 7.4 RC2. The structure of the tables is defined as below:
The behind the such a cascade definition is to restrict the user from deleting a code if its defined as parent code. But its just a child code then a user can delete it. Furthermore, a CODE_ID can have 0 to 1 parent codes. As you can see the highlighted column doesn't have a parent defined in the CODE_RELATION table.
When I generate the entity classes from the database using the wizard the classes are generated like below:
#Entity
#Table(name = "CODES")
public class Codes implements Serializable {
#Id
#Column(name = "CODE_ID", nullable = false, length = 30)
private String codeId;
#Column(name = "CODE_DESC", length = 200)
private String codeDesc;
#Column(name = "ACTIVE", nullable = false)
private char active;
#JoinTable(name = "CODE_RELATION", joinColumns = {
#JoinColumn(name = "CODE_ID", referencedColumnName = "CODE_ID", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "PARENT_CODE_ID", referencedColumnName = "CODE_ID", nullable = false)})
#ManyToMany
private List<Codes> codesList;
#ManyToMany(mappedBy = "codesList")
private List<Codes> codesList1;
//Getter, Setter properties
}
It seems to me a little bit confusing as I can't see the Cascade constraint referenced anywhere in the generated entity. Does this entity exactly reflects my table structure and constraints?
Any suggestions to improve the structure please.
P.S. I am new to java world.

Related

How to join a table with one column of a view in that table's entity class

Scenario:
I have a products table with these fields: id, code, description, photo.
I have a product_stock view from other schema with fields: prod_code, stok_tot
Misson: What I need to do in Spring Boot is to join the products with their total stock.
Rules:
I can't use #Query on this task, I need to join them at the Product Entity Class, so that stock became some kind of Transient field of product.
I can't change the fact that product's ID is a Long and ProductStock's ID is a String, but I could use product's code field instead right? (how?)
So far... I tryed to use #OneToOne and #JoinColumn to do the job, but my REST gives me the stock field as NULL.
"Estoque.java"
#Entity
#Table(name = "VW_ESTOQUE", schema = "ASICAT")
public class Estoque {
#Id
#Column(name = "CD_BEM_SERVICO", unique = true, nullable = false)
private String codigo;
#Column(name = "ESTOQUE")
private Long estoque;
// getters and setters hidden by me
}
"Produto.java"
#Entity
#NamedEntityGraph(name = "Produto.detail", attributeNodes = #NamedAttributeNode("categorias"))
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
// here is where I get the null values
#Transient
#OneToOne(fetch = FetchType.LAZY)
#JoinTable(name = "VW_ESTOQUE", joinColumns = #JoinColumn(name = "CODIGO", referencedColumnName = "CODIGO"), inverseJoinColumns = #JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO"))
private Estoque estoque;
private String hash;
#ManyToMany(mappedBy = "produtos", fetch = FetchType.EAGER)
#BatchSize(size = 10)
private List<Categoria> categorias = new ArrayList<>();
// getters and setters hidden by me
}
In my product repository I call FindAll()
You have annotated Produto.estoque as #Transient, which means that it is not part of the persistent state of the entity. Such a field will be neither written nor read when instances of that entity are managed. That's not going to serve your purpose.
There are two things I can imagine you might have been trying to achieve:
That every time an Estoque is accessed via a Produto, it should be loaded from the DB to ensure its freshness. JPA does not provide for that, though you might want to annotate Estoque with #Cacheable(value = false), and specify the lazy fetch strategy on the Produto side of the relationship.
You want to avoid the persistence provider attempting to persist any changes to an Estoque, since it is backed by a view, not an updatable table. This we can address.
My first suggestion would be to map ASICAT.VW_ESTOQUE as a secondary table instead of an entirely separate entity. That might look something like this:
#Entity
#SecondaryTable(name = "VW_ESTOQUE", schema = "ASICAT"
pkJoinColumns = {
#PrimaryKeyJoinColumn(name = "CD_BEM_SERVICO",
referencedColumnName = "CODIGO") })
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
#Column(name = "ESTOQUE", table = "VW_ESTOQUE", nullable = true,
insertable = false, updatable = false)
private Long estoque;
// ...
}
You might furthermore avoid providing a setter for the estoque property.
But the SecondaryTable approach might not work well if you cannot rely on the ESTOQUE view always to provide a row for every row of PRODUTO, as there will very likely be an inner join involved in retrievals. Moreover, you don't get lazy fetches this way. The main alternative is more or less what you present in your question: to set up a separate Estoque entity.
If you do set up a separate Estoque, however, then I would approach it a bit differently. Specifically,
I would make the relationship bidirectional, so that I could
make the Estoque entity the relationship owner.
Something like this, then:
#Entity
public class Produto implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
private Long id;
private String codigo;
private String descricao;
// must not be #Transient:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "produto", cascade = {
CascadeType.REFRESH
})
private Estoque estoque;
// ...
}
#Entity
#Table(name = "VW_ESTOQUE", schema = "ASICAT")
#Cacheable(value = false)
public class Estoque {
#Id
#Column(name = "CD_BEM_SERVICO", nullable = false,
insertable = false, updatable = false)
private String codigo;
#Column(name = "ESTOQUE", insertable = false, updatable = false)
private Long estoque;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "CD_BEM_SERVICO", referencedColumnName = "CODIGO",
nullable = false, insertable = false, updatable = false, unique = true)
Produto produto;
// getters and setters hidden by me
}
In this case, I would avoid providing setter methods for any of the properties of Estoque, and avoid providing any constructor that allows initial property values to be set. Thus, to a first approximation, the only
way an instance's properties will take non-null values is if they are set by the persistence provider.
Additionally, since you mention Oracle, if you are using TopLink as your persistence provider then you might consider applying its #ReadOnly extension attribute to the Estoque entity, in place of or even in addition to some of these protections against trying to insert into or update the view.

Spring jpa onetomany returns only one element

I am trying to build a bidirectional one to many relationship with the spring data jpa but the list annotated with #onetomany always return one element.
Here is the code for my entities(setters and getters omitted):
#Entity
#Table(name = "sdk_sdk")
public class SDKEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String version;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "sdk")
#OrderBy("order ASC")
private List<SDKFileEntity> fileEntities;
}
And the second entity:
#Entity
#Table(name = "sdk_file")
public class SDKFileEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String fileType;
private Integer sdkId;
public SDKFileEntity() {
}
#ManyToOne
#JoinColumn(name = "id", insertable = false, updatable = false)
private SDKEntity sdk;
I am trying to have a manytoone mapping where the sdkId corresponds to the id from the SDKEntity class.
Whenever I try to get the sdkfiles from the sdkEntity using spring's repository, the size of the list is always 1.
So for example:
SDKEntity entity=repository.findOne(foo);
List<SDKFileEntity> files=entity.getFileEntities();
here the size of files is 1, I have to delete the first element from the database to obtain the second element.
For me the reason here was that a parent entity implemented equals and hashcode
and unfortunately in a way that all existing entities were equal.
And non of the child entities implemented it herself.
So then the #OneToMany relation returned only the first element.
Took me quite some time.
This part of Code looks suspicious
#ManyToOne
#JoinColumn(name = "id", insertable = false, updatable = false)
private SDKEntity sdk;
name = "id" it should be actual column name as written in database column name like this
#JoinColumn(name = "VISIT_ID", referencedColumnName = "ID")
#ManyToOne
private Visit visitId;

Hibernate - How to set an object which has pk and fk to be VARCHAR in database?

I have two entities, the first of which is:
#Entity
#Table(name="E_CMS_CFG")
public class E_CMS_CFG extends BaseEntity{
#Id
#OneToOne(cascade=CascadeType.ALL)//, fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "CFG_TYPE", nullable = false, referencedColumnName = "CFG_TYPE", columnDefinition = "VARCHAR(32)"),
#JoinColumn(name = "CFG_TYPE_ID", nullable = false, referencedColumnName = "ID")
})
private E_CMS_CFG_TYPE cfgType;
The second entity is:
#Entity
#Table(name="E_CMS_CFG_TYPE")
public class E_CMS_CFG_TYPE extends BaseEntity{
#Id
#Column(name = "CFG_TYPE", length = 32)
private String cfgType;
In the first entity, I use columnDefinition = "VARCHAR(32)" on the CFG_TYPE column; however, in the database, it is creating a cfg_type integer column. Is it possible to create a VARCHAR cfg_type column and not an integer? And if it is, how? I am using Sqlite 3.8.10.1 version.
UPDATE:
The link did not help, but if I replace in E_CMS_CFG the #Id with #NaturalId (mutable = false), it works, but it creates it without a primary key.
You could try to use the natural id annotation here. For example:
#NaturalId (mutable = false)
#Column(name = "CFG_TYPE", unique = true, nullable = false, length = 32)
private String cfgType;
But I think you would still have to provide an id property and that would still be an integer value/column in your database.
I think this Question is a duplicate of "how to use id with string type in jpa hibernate"
I hope that helps!

Why is #ManyToMany not working with non-primary key columns?

I have 2 entities - User and Role which have following relations: User has a manytomany relation to itself and a manytomany relation with the Role entity.
#Entity
public class UserEntity implements Serializable {
#Id
#Column(length = 12, columnDefinition = "BINARY(12)", name = "Id", unique = true)
private byte[] id;
#Column(name = "Login", unique = true, nullable = false)
private String login;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Login") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "User_User",
joinColumns = { #JoinColumn(name = "UserParent") },
inverseJoinColumns = { #JoinColumn(name = "UserChild") })
private Collection<UserEntity> children;
...
}
and Role:
public class RoleEntity implements Serializable{
#Id
#Column(name = "Id", unique = true, nullable = false)
private String id;
...
}
The strange thing about the setup of DB is that the User_User relation is based on the binary Id keys
create table if not exists User_User (
UserParent binary,
UserChild binary
);
and the user-role is based on varchars
create table if not exists KNUser_UserRole (
UserLogin varchar,
RoleId varchar,
);
Now, when it runs, the user-user relationship work well. However, when I try to access the collection returned for roles, I get a ClassCastException:
java.lang.ClassCastException: **.entity.UserEntity cannot be cast to [B
at org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor.extractHashCode(PrimitiveByteArrayTypeDescriptor.java:41)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:201)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:205)
at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:114)
at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:79)
at org.hibernate.internal.AbstractSessionImpl.generateEntityKey(AbstractSessionImpl.java:240)
at org.hibernate.engine.internal.StatefulPersistenceContext.getCollectionOwner(StatefulPersistenceContext.java:740)
at org.hibernate.loader.Loader.readCollectionElement(Loader.java:1181)
at org.hibernate.loader.Loader.readCollectionElements(Loader.java:800)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:651)
at org.hibernate.loader.Loader.doQuery(Loader.java:856)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:289)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.loadCollection(Loader.java:2175)
at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:61)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:622)
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:82)
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1606)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:379)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:112)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
It looks like the UserEntity is being cast to some binary(?) thing. However, the first relation between users themselves works fine, but the one with another table is wrong.
I am using different columns of different types to join tables. Is it allowed to do it this way?
Another strange thing is that when I switch the #Id annotation to be on the login field, the roles work fine, no issue, but then of course the self-join PersistentBag key is the Login instead of Id, which breaks the relation and no results are retrieved. But the conversion from UserEntity to the "[B" is not done.
Also if I leave things as in example and change the Id type to String (and the DB to varchar) it also starts working (of course not consistently with the User_User table).
What am I doing wrong? What is the reason for getting the classcastexception in this case? Why it work when I change the byte[] to String? Please let me know if you have any ideas. I do not want to change the DB design cause this would lead to lots migration and compatibility issues for clients already using the DB.
Just a note: the #Id has to be on the Id binary field as otherwise I wouldn't be able to make a self-join (I was unable to point twice to a column not being a primary key see: Is Hibernate ManyToMany self-join possible for non-key columns? getting mappingException).
Cheers
Adam
the referred column in your join table must be unique entry, here if you put #Id on login field then it works fine,but when you change it to different other than #Id column you cant be sure about the entries will be unique.what you can do is,
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Id") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
I think it should work.

Using a JoinColumn Twice for CompositeKey getting Repeated Column Exception

I have the following situation:
I´m trying to build an application which is multi-tenant with the same tenants in one database
with the same tables. As by know Hibernate is not supporting this variant before 5.0 as I found.
I`m trying to solve this by adding a brandId field to every table.
As I build Many To Many relationships I also added this brandId to the ManyToMany Join Table and here (dont know if I can do this, mysql is not complaining) I made a foreign key to both tables while both include the brandid
So now for example I have a table Text(ID,name,brandId) and a Tag(ID,name,brandId) and a join table (text_id,tag_id,brand_id) where the foreign keys are
CONSTRAINT FK_TAG_TEXTS_TAG FOREIGN KEY (TAG_ID,BRAND_ID) REFERENCES TAG (ID,brand),
CONSTRAINT FK_TAG_TEXTS_TEXT FOREIGN KEY (TEXT_ID,BRAND_ID) REFERENCES TEXT (ID,brand)
As you can see Brand ID is used twice.
Then I generated my classes with Hibernate Tools, which created a Composite Primary Key Class as it should and the association in the Tag Class.
#ManyToMany(fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST,CascadeType.MERGE })
#JoinTable(name = "tag_texts", , joinColumns = {
#JoinColumn(name = "TAG_ID", nullable = false, insertable = false, updatable = false),
#JoinColumn(name = "BRAND_ID", nullable = false, insertable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "TEXT_ID", insertable = false, nullable = false, updatable = false),#JoinColumn( name = "BRAND_ID", insertable = false, nullable = false, updatable = false) })
public List<Text> getTexts() {
return this.texts;
}
The problem is now that I get the following exception:
org.hibernate.MappingException: Repeated column in mapping for collection: de.company.domain.Tag.texts column: brand_id
I looked into the Hibernate code in the Collection class which raises the exception.
Here a method 'checkColumnDupliation' is called which uses a Set and inserts the name,
what means that a second time inserting "BRAND_ID" as column leads to this behaviour.
As I found the most common solution for the Repeated column error is by inserting 'insertable = false and updateable = false' when using the same column in several references. This is described here:
Hibernate: Where do insertable = false, updatable = false belong in composite primary key constellations involving foreign keys?
But this seems to be not the same problem as mine.
So my question is: Is there a possibility to fix this with the JPA Annotations and use the Brand ID in both joinColumns and inverseJoinColumns?
The problem is that you want a JoinTable between 3 entities: Text, Tag and Brand.
Probably you will have to use an IdClass, something like :
public class AssociationId implements Serializable {
private long textId;
private long tagId;
private long brandId;
hash and equals function
...
}
Id Class Entity:
#Entity
#Table(name="tag_text_brand")
#IdClass(AssociationId.class)
public class TagTextBrandAssociation {
#Id
private long tagId;
#Id
private long textId;
#Id
private long textId;
#ManyToOne
#PrimaryKeyJoinColumn(name="TAG_ID", referencedColumnName="ID")
private Tag tag;
#ManyToOne
#PrimaryKeyJoinColumn(name="TEXT_ID", referencedColumnName="ID")
private Text text;
#ManyToOne
#PrimaryKeyJoinColumn(name="BRAND_ID", referencedColumnName="ID")
private Brand brand;
...
}
You can use this in your 3 entities like this:
#Entity
public class Text {
#Id
private long id;
...
#OneToMany(mappedBy="text")
private List<TagTextBrandAssociation> tagsAndBrands;
...
}
See here for more information.

Categories

Resources