Spring JPA #JoinColumn(unique = true) not working - spring

My entities are as follows
#Entity
#Table(
name = "t1",
uniqueConstraints = {
#UniqueConstraint(name = "u1", columnNames = {"u1"}),
#UniqueConstraint(name = "u2", columnNames = {"u2_id"}) //This does not enforce uniqueness
}
)
public class t1
{
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String u1; //Uniqueness is enforced here
#OneToOne
#JoinColumn(unique = true) //This does not enforce uniqueness
private U2 u2;
}
#Entity
#Table(
name = "u2",
uniqueConstraints = {
#UniqueConstraint(name = "p1", columnNames = {"p1"})
}
)
public class u2
{
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String p1; //Uniqueness is enforced here
}
Creating and updating the entities by
t1 t1_0 = new t1("t1_0");
t1 t1_1 = new t1("t1_1");
u2 u2_0 = new u2("u2_0");
repository.save(t1_0);
repository.save(t1_1);
repository.save(u2_0);
t1_0 = repository.findOne(t1_0.getId());
t1_1 = repository.findOne(t1_1.getId());
t1_0.setu2(u2_0);
t1_1.setu2(u2_0);
After the transaction finishes t1_0.u2 is tied to u2_0 and t1_1.u2 is tied to u2_0. I expected that it would throw uniqueness constraint violation exception.
Do not understand what is wrong. Other threads on SO suggest that #JoinColumn(unique = true) should do it.
EDIT
This is wrapped in one transaction
t1_0 = repository.findOne(t1_0.getId());
t1_1 = repository.findOne(t1_1.getId());
t1_0.setu2(u2_0);
t1_1.setu2(u2_0);
EDIT2
For some reason I can't access the database either. The console view loads but after adding password and user blank page is shown (some network queries fail).
But I can see from log that foreign keys are created and unique constraint aswell
Hibernate: alter table driver add constraint UK_ssh305wwvomjtn6opolug33nj unique (cardo_id)
Hibernate: alter table car add constraint FKosnia01vhqwmm888uxrg4o6f6 foreign key (manufacturerdo_id) references manufacturer
Hibernate: alter table driver add constraint FK3yb5ci9sr6ieo6n4wwwef3puv foreign key (cardo_id) references car
When adding

My transactions were invalid. As they do not see each others data, I mistakenly created one within another.

Columns names (if more than one) should be introduced with commas.
Your convention is wrong
Disclaimer: not checked on real database
#Table(
name = "t1",
uniqueConstraints = {
#UniqueConstraint(name = "u1", columnNames = {"u1"}),
#UniqueConstraint(name = "u2", columnNames = {"u2", "id"}) // comma
}
)

Related

chained transaction throw table doesn't exist when using TableGenerator

i need your help,
i'm using a chained transaction for insering in two database simultanasly, the problem i have is my entity is created with tableGenerator from my id and when i'm using chained transaction it goes to my first database searching for this table and throws table doesn't exist because my table is in the second database not the first. i was hoping that chained transaction is intelligent enough to know which database search for this table but its not the case. can someone please help how can i acheive that ?
#Entity
public class Group {
#Id
#Column(name = "groupId", updatable = false, insertable = false)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "counter")
#TableGenerator(name = "counter",
table = "Counter",
valueColumnName = "currentId",
pkColumnName = "name",
pkColumnValue = "com.model.Counter",
allocationSize = 1)
private Long groupId;
}
#Override
public void createGroup(Group group) {
create(group);
}
#Transactional(value = "chainedDatabaseManager")
public void creerLiferyGroup(Group group, User user){
//database 1 insert
UserService.createUser(user);
//database2 insert
groupService.createGroup(group);
}
The exception is Table on database1.Counter doesn't exist which is normal because my Table Counter is in my second database with the Group entity

Spring boot JPA - Insert or update a list of entities

I have a repo with a unique constraint on 2 fields, connection_id and token_type:
#Entity
#Table(
name = "business_api_token",
schema = "public",
uniqueConstraints = {
#UniqueConstraint(
name = "business_api_token_unique_connection_id_and_token_type",
columnNames = {"connection_id", "token_type"}
)
}
)
public class BusinessApiToken {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#JoinColumn(
name = "connection_id",
nullable = false,
foreignKey = #ForeignKey(
name = "fk_business_api_token_connection_id"
)
)
private AccountingConnection connection;
#Column(name = "token_type")
#Enumerated(EnumType.STRING)
private ApiTokenType tokenType;
#Column(name = "token_value")
private String tokenValue;
...
}
I saw some posts saying add a custom query, something like this:
#Modifying
#Query("update User u set u.firstname = ?1, u.lastname = ?2 where u.id = ?3")
void setUserInfoById(String firstname, String lastname, Integer userId);
But how would I do this for a list? I was doing this:
businessApiTokenRepository.saveAll(tokens)
Which gives an error.
The tokens are created elsewhere without knowledge of existing ones, I can do another query to check first but that seems inefficient, and I have to do this all over.
Thanks

Why Value is not getting assigned in JPA for insert statement

Hi I have couple of Entity classes as below, using lombok for getter and setters
Parent Entity Class have
#Table(name = "PARTY")
#Entity
public class Party {
#Id
#Column(name = "PARTY_ID")
private Long partyId;
#OneToMany(targetEntity = DVLoanParticipants.class,cascade = CascadeType.ALL)
#JoinColumn(name = "PARTY_ID")
#MapKey(name="dvpParticipantName")
#LazyCollection(LazyCollectionOption.FALSE)
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
}
Child Entity Class have
#Table(name = "DV_LOAN_PARTICIPANTS")
#Entity
public class DVLoanParticipants implements Serializable {
#Id
#Column(name = "PARTY_ID")
private Long partyId;
#Id
#Column(name = "DVP_PARTICIPANT_NAME")
private String dvpParticipantName;
#Column(name = "DVP_PARTICIPANT_TYPE")
private String dvpParticipantType;
}
In service class i am calling save operation as
repository.save(parentEntityObject);
I am able to execute update statements ,but when i try to insert new row for child entity class i am getting an error saying
cannot insert NULL into ("ABC"."DV_LOAN_PARTICIPANTS"."PARTY_ID")
But if i print the parentEntityObject just before the save operation i see the values like
(partyId=12345678, dvpParticipantName=XYZ, dvpParticipantType=VKP)
I see the query formed as
insert
into
DV_LOAN_PARTICIPANTS
(DVP_PARTICIPANT_TYPE, PARTY_ID, DVP_PARTICIPANT_NAME)
values
(?, ?, ?)
Just before te save i am seeing valules in the Object
Builder=DVLoanParticipants(partyId=123456, dvpParticipantName=Builder,
dvpParticipantType=Individual)
Update
This is the setting part for values
DVLoanParticipants dvLoanParticipants = new
DVLoanParticipants();
dvLoanParticipants.setPartyId(Long.valueOf(partyId));
dvLoanParticipants.setDvpParticipantName("Builder");
dvLoanParticipants.setDvpParticipantType("Individual");
Party party = new Party();
Map<String, DVLoanParticipants> dvLoanParticipantsMap = new
java.util.HashMap<>();
dvLoanParticipantsMap.put("Builder", dvLoanParticipants);
party.setPartyId(Long.valueOf(partyId));
party.setDvLoanParticipantsMap(dvLoanParticipantsMap);
repository.save(party);
What is the mistake i am doing ?
The root cause of your problem in this part:
#OneToMany(targetEntity = DVLoanParticipants.class,cascade = CascadeType.ALL)
#JoinColumn(name = "LOAN_ID")
#MapKey(name="dvpParticipantName")
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
actually for your case the column name in the #JoinColumn means:
If the join is for a unidirectional OneToMany mapping using a foreign key mapping strategy, the foreign key is in the table of the target entity.
So, assuming for the clarity that you want to map the following schema:
create table PARTY
(
PARTY_ID int,
-- ...
primary key (PARTY_ID)
);
create table DV_LOAN_PARTICIPANTS
(
PARTY_ID int,
DVP_PARTICIPANT_NAME varchar(50),
DVP_PARTICIPANT_TYPE varchar(10),
-- ...
primary key (PARTY_ID, DVP_PARTICIPANT_NAME),
foreign key (PARTY_ID) references PARTY(PARTY_ID)
);
You can use the following mapping:
#Entity
#Table(name = "PARTY")
public class Party
{
#Id
#Column(name = "PARTY_ID")
private Long partyId;
// I use fetch = FetchType.EAGER instead of deprecated #LazyCollection(LazyCollectionOption.FALSE)
// targetEntity = DVLoanParticipants.class is redundant here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "PARTY_ID") // this is DV_LOAN_PARTICIPANTS.PARTY_ID column
#MapKey(name = "dvpParticipantName")
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
public Party()
{
dvLoanParticipantsMap = new HashMap<>();
}
// getters / setters
public void addParticipant(DVLoanParticipants p)
{
this.dvLoanParticipantsMap.put(p.getDvpParticipantName(), p);
p.setPartyId(getPartyId());
}
}
#Entity
#Table(name = "DV_LOAN_PARTICIPANTS")
public class DVLoanParticipants implements Serializable
{
#Id
#Column(name = "PARTY_ID")
private Long partyId;
#Id
#Column(name = "DVP_PARTICIPANT_NAME")
private String dvpParticipantName;
#Column(name = "DVP_PARTICIPANT_TYPE")
private String dvpParticipantType;
// getters / setters
}
and example how to save:
Party party = new Party();
party.setPartyId(2L);
// ...
DVLoanParticipants part1 = new DVLoanParticipants();
part1.setDvpParticipantName("Name 3");
part1.setDvpParticipantType("T1");
DVLoanParticipants part2 = new DVLoanParticipants();
part2.setDvpParticipantName("Name 4");
part2.setDvpParticipantType("T1");
party.addParticipant(part1);
party.addParticipant(part2);
repository.save(party);
and several notes:
The LazyCollectionOption.TRUE and LazyCollectionOption.FALSE values are deprecated since you should be using the JPA FetchType attribute of the #OneToMany association.
You use hibernate specific approach for mapping сomposite identifiers. As it's mentioned in the hibernate documentation:
The restriction that a composite identifier has to be represented by a primary key class (e.g. #EmbeddedId or #IdClass) is only JPA-specific.
Hibernate does allow composite identifiers to be defined without a primary key class via multiple #Id attributes.
But if you want to achieve more portability you should prefer one of the jpa allowed approaches.

JPA mapping :Primary key column of one table to non Pk/Fk column of another table

#Entity
#Table(name = "TableA")
public class TableAEntity{
#Id
#Column(name = "RUL_ID"
private Integer rulId;
#Column(name = "COMMENT"
private Integer comment;
#OneToOne
#JoinColumn(name = "RUL_ID" referencedColumnName ="PRNT_ID", insertable=false, updatable=false)
private TableBEntity tableB;
//GETTERS AND SETTERS
}
#Entity
#Table(name = "TableB")
public class TableBEntity{
#Id
#Column(name = "ADD_ID"
private Integer addID;
#Column(name = "PRNT_ID"
private Integer prntId;
//GETTERS AND SETTERS
}
There are 2 DB tables.
TableA with primary key as rulId.
TableB with primary key as addID.
I have to implement a customized JOIN query using JPA native query.
Java Code is:
StringBuilder querySql = "select a.rulId, b.prntId from TableA a JOIN TableB b ON a.rulID = b.prntId"
Query tabQuery = entityManager.createNativeQuery(querySql.toString, TableAEntity.class)
List<TableAEntity> entityList = tabQuery.getResultList();
How to establish this OneToOne(TableA:TableB) relationship when they are not linked with any key(pk/fk).
I am unable to map ResultList to my entity class.Primary key of TableA "rulId" always gets linked to PrimaryKey of TableB "addId", wherein I want to get it associated to "prntId".
Can anyone please help on this.
A couple of things to note:
For JPA query, you have to use createQuery (createNativeQuery is for SQL queries);
#Table(name=...) will define the name of the table in the database but not when you write a JPQL query. For that you can use #Entity(name="..."). In your case, it should be #Entity(name="TableA");
The return value of the query is two fields, not TableAEntity. So passing it as parameter to createQuery is wrong;
It's weird to return a.id and b.id. If you want the entities, you can return a and b.
If there is an association between TableA and TableB, for example:
#Entity(name = "TableA")
public class TableAEntity {
...
#OneToOne
#JoinColumn(referencedColumnName ="PRNT_ID", insertable=false, updatable=false)
public TableBEntity tableB;
}
then you can run the following query:
String jpqlQuery = "from TableA a join fetch a.tableB b";
List<TableAEntity> entityList = entityManager.createQuery(jpqlQuery, TableAEntity.class).getResultList()
entityList.foreach( tableAEntity -> {
TableBEntity tabB = tableAEntity.tableB;
});
If there is no association between TableA and TableB:
String jpqlQuery = "select a, b from TableA a JOIN TableB b ON a.rulID = b.prntId";
List<Object[]> entityList = entityManager.createQuery(jpqlQuery).getResultList()
entityList.foreach( row -> {
TableAEntity tabA = (TableAEntity) row[0];
TableBEntity tabB = (TableBEntity) row[1];
});
But if you really just need the ids, this will work too:
String jpqlQuery = "select a.rulId, b.prntId from TableA a JOIN TableB b ON a.rulID = b.prntId";
List<Object[]> entityList = entityManager.createQuery(jpqlQuery).getResultList()
entityList.foreach( row -> {
Integer tabAId = (Integer) row[0];
Integer tabBId = (Integer) row[1];
...
});
Note that you can change the select and mix the two approaches.
But because there is an association between TableAEntity and TableBEntity, you could rewrite all this as:
String jpqlQuery = "from TableA";
List<TableAEntity> entityList = entityManager.createQuery(jpqlQuery,
TableAEntity.class).getResultList()
entityList.foreach( entity -> {
TableAEntity tabA = entity;
TableBEntity tabB = entity.getTableB();
...
});
With or without the association, you can return from the select clause all the combinations of values you need:
select a, b.addID from ...
select a, b from ...

Querying composite table in Hibernate

I am working on a Spring-MVC application where I have a many-to-many relationship in which I have to query in 2 tables to get the values I require. I will explain in more detail.
I have 2 tables GroupAccount, GroupMembers with many-to-many
relationship. Now there is a junction table called membertable where
id from GroupMembers and GroupAccount is stored.
This is what I am looking for :
I pass a groupAccounId and username as parameters. Now, in the
GroupMembers table, there is a username stored. In groupAccount,
there is groupAccountId is stored.
Now in the memberjunction, I have composite key
memberid,GroupAccountId, I would like the member id for the username
which has a matching groupAccountId I submit.
Below is the SQL code and Spring-mvc code to understand more better.
CREATE TABLE public.groupaccount (
groupid NUMERIC NOT NULL,
groupname VARCHAR,
groupaccountstatus BOOLEAN DEFAULT false NOT NULL,
adminusername VARCHAR,
CONSTRAINT groupid PRIMARY KEY (groupid)
);
CREATE TABLE public.groupmembers (
memberid INTEGER NOT NULL,
musername VARCHAR
CONSTRAINT memberid PRIMARY KEY (memberid)
);
CREATE TABLE public.memberjunction (
memberid INTEGER NOT NULL,
groupid NUMERIC NOT NULL,
CONSTRAINT membergroupid PRIMARY KEY (memberid, groupid)
);
GroupMembersDAOImpl :#
#Override
public List<Integer> returnMemberIdWithMatchingUsername(String memberUsername) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("From GroupMembers as " +
"n where n.memberUsername=:memberUsername");
query.setParameter("memberUsername",memberUsername);
List<GroupMembers> memberList = query.list();
List<Integer> memberIdList = new ArrayList<>();
for(GroupMembers members :memberList){
memberIdList.add(members.getMemberid());
}
return memberIdList;
}
GroupAccount model :
#Entity
#Table(name="groupaccount")
public class GroupAccount {
#Id
#Column(name="groupid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupaccount_seq_gen")
#SequenceGenerator(name = "groupaccount_seq_gen",sequenceName = "groupaccount_seq")
private Long groupId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "memberjunction", joinColumns = {#JoinColumn(name = "groupid")},
inverseJoinColumns = {#JoinColumn(name = "memberid")})
private Set<GroupMembers> groupMembersSet = new HashSet<>();
public void setGroupMembersSet(Set<GroupMembers> groupMembersSet){
this.groupMembersSet = groupMembersSet;
}
}
GroupMembers model class :
#Entity
#Table(name="groupmembers")
public class GroupMembers {
#Id
#Column(name="memberid")
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "groupmembers_seq_gen")
#SequenceGenerator(name = "groupmembers_seq_gen",sequenceName = "groupmembers_seq")
private int memberid;
#ManyToMany(mappedBy = "groupMembersSet")
private Set<GroupAccount> groupAccounts = new HashSet<>();
public void setGroupAccounts(Set<GroupAccount> groupAccounts){
this.groupAccounts = groupAccounts;
}
public Set<GroupAccount> getGroupAccounts(){
return this.groupAccounts;
}
}
Query I am using :
#Override
public int getMemberIdForCanvas(String memberUsername, Long groupId) {
session = this.sessionFactory.getCurrentSession();
org.hibernate.Query query = session.createQuery("select distinct m.memberId from GroupMembers m\n" +
"join m.groupAccounts a\n" +
"where a.memberUsername = :userName and m.groupId=:groupId");
query.setParameter(memberUsername,"memberUsername");
query.setParameter(String.valueOf(groupId),"groupId");
int memberid = (Integer)query.uniqueResult();
return memberid;
}
Any help would be nice. Thanks a lot.
Here's the documentation for joins and HQL. Please read it.
The query is as simple as
select distinct m.memberId from GroupMembers m
join m.groupAccounts a
where a.memberUsername = :userName
Please also fix your naming. A GroupMembers instance is a single group member. So the class should be named GroupMember, without s. Repeating the name of the class in the fields of this class is also redundant: member.getId() is more readable and less verbose than member.getMemberId(). Same for the other fields.

Resources