I have two entities, fileVersion and fileEnvironment, which have a many to many relationship. I'm using a junction table, modeled by fileDeployment entity.
The junction entity:
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(
name = "file_deployment"
)
public class FileDeploymentEntity {
#EmbeddedId
private FileDeploymentKey id;
#ToString.Exclude
#ManyToOne
#MapsId("fileVersionId")
#JoinColumn(name = "fileVersionId")
private FileVersionEntity fileVersion;
#ToString.Exclude
#ManyToOne
#MapsId("fileEnvironmentId")
#JoinColumn(name = "fileEnvironmentId")
private FileEnvironmentEntity fileEnvironment;
}
It's composite key:
#Embeddable
#NoArgsConstructor
#AllArgsConstructor
#Data
#Builder(toBuilder = true)
public class FileDeploymentKey implements Serializable {
#Column
private UUID fileVersionId;
#Column
private UUID fileEnvironmentId;
}
Its JPA repository:
#Repository
public interface FileDeploymentEntityRepository extends
JpaRepository<FileDeploymentEntity, FileDeploymentKey>,
JpaSpecificationExecutor<FileDeploymentEntity> {
}
The two entities for which the junction entity is capturing the many-to-many relationship for:
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(
name = "file_environment"
)
public class FileEnvironmentEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "uuid2")
private UUID id;
#ToString.Exclude
#OneToMany(mappedBy = "fileEnvironment")
private List<FileDeploymentEntity> fileDeployments;
}
FileVersion is the other
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(
name = "file_version"
)
public class FileVersionEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "uuid2")
private UUID id;
#ToString.Exclude
#OneToMany(mappedBy = "fileVersion")
private List<FileDeploymentEntity> fileDeployments;
}
The following code executes fine:
var fileDeploymentEntity = FileDeploymentEntity.builder()
.id(FileDeploymentKey.builder()
.fileVersionId(existingFileVersion.get().getId())
.fileEnvironmentId(existingFileEnvironment.get().getId())
.build())
.deploymentTime(
Instant.now(clock))
.fileEnvironment(existingFileEnvironment.get())
.fileVersion(existingFileVersion.get())
.build();
var result = fileDeploymentEntityRepository.save(fileDeploymentEntity);
But when eventually fileDeploymentEntityRepository.flush() is called I get the following exception:
could not execute statement; SQL [n/a]; constraint [id]
org.hibernate.exception.ConstraintViolationException: could not
execute statement
org.postgresql.util.PSQLException: ERROR: null value in column "id"
violates not-null constraint Detail: Failing row contains
(7670fec3-3766-4c69-9598-d4e89b5d1845,
b9f6819e-af89-4270-a7b9-ccbd47f62c39, 2019-10-15 20:29:10.384987,
null, null, null, null).
If I also call save for the 2 entities it doesn't change the result:
fileVersionEntityRepository
.save(existingFileVersion.get().addFileDeployment(fileDeploymentEntity));
fileEnvironmentEntityRepository
.save(existingFileEnvironment.get().addFileDeployment(fileDeploymentEntity));
Any help would be greatly appreciated!
For me the issue was that I named another entity with the same table name by accident which caused the schema that was generated to be very different from what I thought it was.
Take away lesson:
1) Check the schema that is generated when in doubt.
var con = dataSource.getConnection();
var databaseMetaData = con.getMetaData();
ResultSet resultSet = databaseMetaData.getTables(null, null, null, new String[]{"TABLE"});
System.out.println("Printing TABLE_TYPE \"TABLE\" ");
System.out.println("----------------------------------");
while(resultSet.next())
{
//Print
System.out.println(resultSet.getString("TABLE_NAME"));
}
ResultSet columns = databaseMetaData.getColumns(null,null, "study", null);
while(columns.next())
{
String columnName = columns.getString("COLUMN_NAME");
//Printing results
System.out.println(columnName);
}
Related
Let's imagine that we have two entities in the database. We have a simple Server with a unique Id:
PostgreSQL:
CREATE TABLE public.servers (
id bigint NOT NULL,
name character varying(64) NOT NULL
);
ALTER TABLE ONLY public.servers
ADD CONSTRAINT servers_pkey PRIMARY KEY (id);
Class entity:
#Entity
#Table(name = "servers")
public class Server {
#Id
private long id;
private String name;
#OneToMany(mappedBy = "server", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Channel> channels;
public Server() {}
public Server(String name) {
this.name = name;
channels = new ArrayList<>();
}
// Getters and Setters...
}
Each Server can have several Channels that also have a unique id and belong to a server:
PostgreSQL:
CREATE TABLE public.channels (
id bigint NOT NULL,
server_id bigint NOT NULL,
name character varying NOT NULL
);
ALTER TABLE ONLY public.channels
ADD CONSTRAINT channels_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.channels
ADD CONSTRAINT fkey_channel_server FOREIGN KEY (server_id) REFERENCES public.servers(id);
Class entity:
#Entity
#Table (name = "channels")
public class Channel {
#Id
private long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "server_sn")
private Server server;
public Channel() {}
public Channel(Server server, String name) {
this.server = server;
this.name = name;
}
// Getters and Setters...
}
Next, I create a View in PostgreSQL:
CREATE VIEW public.summary AS
SELECT servers.s_snowflake AS server_id, channels.c_snowflake AS channel_id, channels.name FROM servers
JOIN channels ON servers.s_snowflake = channels.server_sn;
I want to get data from this view but I'm facing a problem on how to implement the class. I have tried something like this:
#Embeddable
class SummaryPK implements Serializable {
private long server_id;
private long channel_id;
}
#Entity
#Immutable
#Subselect("SELECT * FROM summary")
public class Summary {
#EmbeddedId
private SummaryPK summaryPK;
#MapsId("server_id")
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Server.class)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Server server;
#MapsId("channel_id")
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Channel.class)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Channel channel;
private String name;
// Getters and Setters
}
In the program, I want to receive a list of Entity, for example, by passing the Server ID. Any idea how to implement a working structure here?
Fixed the problem, there were incorrect id in JoinColumn:
#JoinColumn(name = "server_id", insertable = false, updatable = false)
#JoinColumn(name = "channel_id", insertable = false, updatable = false)
i'm kinda struggling mapping the following schema with hibernate
table_a (A1_ID,A2_ID) --> PK = (A1_ID, A2_ID)
table_b (A1_ID, A2_ID, B1_ID) --> PK =(A1_ID, A2_ID, B1_ID)
where table_b's A1_ID and A2_ID should be foreingkey referencing respective table_A's columns
There is a one-to-many from TABLE_A to TABLE_B where TABLE_B's primary key is partially shared with TABLE_A's primary key
What I've tried so far
#Data
#Entity
#Table(name = "table_a")
#IdClass(TableA.TableAKey.class)
public class TableA {
#Id
#Column(name = "A1_ID)
private String a1_id;
#Id
#Column(name = "A2_ID)
private String a2_id;
#OneToMany(mappedBy = "tableA",fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<TableB> tableB;
#Data
static class TableAKey implements Serializable {
private String a1_id;
private String a2_id
}
}
**CHILD ENTITY**
#Data
#Entity
#Table(name = "table_b")
#IdClass(TableB.TableBKey.class)
public class TableB {
#Id
#Column(name = "B1_ID)
private String b1_id;
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "a1_id", insertable = false, updatable = false),
#JoinColumn(name = "a2_id", insertable = false, updatable = false)
)}
private TableA tableA;
#Column(name = "A1_ID)
private String a1_id;
#Column(name = "A2_ID)
private String a2_id;
#Data
static class TableAKey implements Serializable {
private String b1_id;
private TableA tableA;
}
}
I was expecting i could be able to do something like this:
TableA tableA = new TableA();
t.setA1_id("a1id");
t.setA2_id("a2id");
TableB tableB = new TableB();
tableB.setB1Id("b1Id");
tableA.setTableB(Arrays.asList(tableB));
tableARepository.save(tableA);
And the code above I was expecting to "magically" perform the following insert at DB
INSERT INTO table_A (A1_ID,A2_ID) VALUES ('a1id',a2id');
INSERT INTO table_B (A1_ID,A2_ID, B1_ID) VALUES ('a1id',a2id','b1id')
but instead i get a "the column index is out of range: n, number of columns n-1".
I also tried with some embeddedId approach, using referenceColumnName but nothing.
Am I doing something wrong in the mapping or in the object creation process?
The problem is a lot similar to the following
https://hibernate.atlassian.net/browse/HHH-14340
I am trying to map a non primary key column between 2 tables using one-to-one mapping through JPA.
The OneToOne is not performing the join on the mentioned column rather it is picking up the Id field.
Below is the table structure:
Person table
id (PK)
name
college
College table
id (PK)
clg_name
location
Location table
id (PK)
loc_name
I need to provide OneToOne mapping between College and Location using the columns location and loc_name respectively. I have tried using the #NaturalId, #MapsId and by providing the reference column name. Still it uses the id field
//person
#Entity
#Table(name = "PERSON", schema = "DETAILS")
#SecondaryTables({
#SecondaryTable(name = "COLLEGE", schema = "DETAILS")
})
class Person{
Person(){
this.college = new College();
}
#Id
#Column(name = "ID", nullable = false)
private Long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "COLLEGE_NAME", table = "COLLEGE", nullable = false)
private String college;
#OneToOne(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER, mappedBy = "person")
#JoinColumn(name = "ID")
private College college;
//getter setters
}
//college
#Entity
#Table(name = "COLLEGE", schema = "DETAILS")
class College{
College(){
}
#Id
#MapsId
#OneToOne()
#JoinColumn(name = "ID")
private Person person;
#OneToOne(cascade = {CascadeType.ALL}, fetch = FetchType.EAGER, mappedBy = "college")
#JoinColumn(referencedColumnName = "LOC_NAME")
private Location location;
#Column(name = "LOCATION", nullable = false)
private String loc;
//getter setters
}
//location
#Entity
#Table(name = "LOCATION", schema = "DETAILS")
class Location{
Location(){}
#Id
#Column(name = "ID")
private Long collegeId;
#MapsId
#OneToOne()
#JoinColumn(name = "LOC_NAME", referencedColumnName ="LOCATION", nullable = false, unique = true)
private College college;
#Column(name = "LOC_NAME", nullable = false)
private String locName;
//getter setters
}
In the above code, I am facing in OneToOne mapping issue using the location name columns. I am querying the Person object from the JPA repository by querying "from Person p where p.id = :id".
The generated JPA queries in logs for 1to1 mapping appears to be
select from details.college college0_ left outer join details.location location1_ on college0_.id=location1_.locName where college0_.id=?
Error:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet] with root cause
Caused by: java.sql.SQLSyntaxErrorException: ORA-01722: invalid number
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:447)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:396)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:951)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:513)
If I remove #MapsId from Location then I get below error:
org.hibernate.AnnotationException: A Foreign key refering has the wrong number of column. should be 0
I have two entities with a relationship, UserEntity:
#Embeddable
public class UserId extends EntityId implements Serializable {
#Column( length = CCEntity.ID_MAX_SIZE, name = ccIdCN )
private String ccId;
#Column( length = NAME_MAX_SIZE, name = userIdCN )
private String userId;
...
}
#Entity
#Table(name = TableNames.CC_Users)
public class UserEntity {
#EmbeddedId
private UserId id;
...
#OneToMany(targetEntity = ProfileEntity.class, mappedBy = "user", fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE })
private List<ProfileEntity> profiles;
And the ProfileEntity:
#Embeddable
public class ProfileId extends EntityId implements Serializable {
#Column( length = CCEntity.ID_MAX_SIZE, name = ccIdCN )
private String ccId;
#Column( length = NAME_MAX_SIZE, name = profileIdCN )
private String profileId;
....
}
#Entity
#Table(name = TableNames.CC_Profile)
public class ProfileEntity {
#EmbeddedId
protected ProfileId id;
...
#ManyToOne
#JoinColumns(value = {
#JoinColumn( nullable = true, name = Columns.referenceIdCN, referencedColumnName = UserId.userIdCN ),
#JoinColumn( nullable = true, name = Columns.ccIdOfReferenceCN, referencedColumnName = UserId.ccIdCN ),
})
private UserEntity user;
When JPA creates the tables it generates the following:
Table CC_USER with primary key: cc_id, user_id. That is correct.
Table CC_PROFILE with primary key: cc_id, user_id, profile_id. Here I don't understand why JPA adds the user_id column as primary key. The table also has the columns: reference_id and cc_id_of_reference_id set as nullable.
I want property user of the ProfileEntity is optional, or nullable. If I try to add an entity with the user as null, I get :
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "user_id" violates not-null constraint
thanks for any help
Finally I found the issue. I have another Entity with same TableName as ProfileEntity, but a different id. That id contains the user_id column that wasn't expected.
Owner:
#Entity
public class Strategy implements Serializable {
#Id
#GeneratedValue
private Long id;
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST})
#JoinTable(name = "StrategyHost", joinColumns = {#JoinColumn(name = "strategyId")}, inverseJoinColumns = {#JoinColumn(name = "hostId")})
private Set<Host> hostName;
}
Related entity:
#Entity
public class Host {
#Id
#GeneratedValue
private Long id;
#Column(unique = true)
private String name;
#ManyToMany(mappedBy = "hostName")
private List<Strategy> strategies;
public Host(String name) {
this.name = name;
}
}
Test:
#Test
#Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testStrategyWithHosts() {
Strategy s = new Strategy();
Set<Host> hosts= new HashSet<>();
hosts.add(Host.builder().name("aaa").build());
hosts.add(Host.builder().name("bbb").build());
s.setHostName(hosts);
Strategy saved= strategyDao.save(s);
Set<Host> hostName = saved.getHostName();
}
debug shows the persisted saved object having Host:
Where are name values? However, if I add merge in cascade type array, name are valued. Why insert (not update managed entities) operation for related entities must have merge cascade type? Although log shows nothing suspicious:
insert into strategy...
insert into host...
insert into host...
update strategy ...
insert into strategy_host ...
insert into strategy_host ...