I have this table and, as the code shows, I have marked columns "cnpj, product, proposalNumber" as a unique composed constraint:
#Table(name = "Proposal", uniqueConstraints = {#UniqueConstraint(columnNames = {"cnpj", "product", "proposalNumber"})})
public class Proposal {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, updatable = false, insertable = false)
#JsonProperty("id")
private Long id;
#JsonProperty("cnpj")
#Column(name = "cnpj", nullable = false, length = 14)
private String cnpj;
#JsonProperty("proposalNumber")
#Column(name = "proposalNumber", nullable = false)
private String proposalNumber;
#JsonProperty("product")
#Column(name = "product", nullable = false, length = 100)
private String product;
#JsonProperty("price")
#Column(name = "price", nullable = false)
private BigDecimal price;
#JsonProperty("dueDate")
#Column(name = "dueDate", nullable = false)
private String dueDate;
#JsonProperty("qtyLife")
#Column(name = "qtyLife", nullable = false)
private Integer qtyLife;
#JsonIgnore
#Column(name = "active", nullable = false)
private Boolean active = true;
...
But, checking the DDL or DUMP ain't no unique information...
CREATE TABLE `proposal` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`active` bit(1) NOT NULL,
`cnpj` varchar(14) NOT NULL,
`due_date` varchar(255) NOT NULL,
`price` decimal(19,2) NOT NULL,
`product` varchar(100) NOT NULL,
`proposal_number` varchar(255) NOT NULL,
`qty_life` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
In addition to this, writing #Column(unique = true) on the column does not solve the problem either, it creates the unique constraint in the database but only referencing that single column, not the composition (cnpj, product and proposalNumber).
Any tips?
The problem is solved: nothing wrong with my solution, but I had to change my spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect to spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
Related
I am trying to do one to many association by a non primary key of the parent. I am using JPA 2.1 with hibernate. I have found several similar questions. But i think my case is a bit different.
I have two table :
ProfileBasic and Phonenumber.
#Entity
public class ProfileBasic {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "profile_id")
private Long id;
//....some columns.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "profile_id")
private List<PhoneNumber> phone_number;
// getters-setters
}
public class PhoneNumber implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// getters-setters and other columns
}
Database tables :
CREATE TABLE `profilebasic` (
`profile_id` bigint(20) NOT NULL,
`available` varchar(255) DEFAULT NULL,
`birth_date` varchar(255) DEFAULT NULL,
`blood_Group` varchar(255) DEFAULT NULL,
`care_of` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`gender` varchar(255) DEFAULT NULL,
`marital_status` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`profession` varchar(255) DEFAULT NULL,
`religion` varchar(255) DEFAULT NULL,
`user_id` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Indexes for table `profilebasic`
--
ALTER TABLE `profilebasic`
ADD PRIMARY KEY (`profile_id`);
CREATE TABLE `phonenumber` (
`id` bigint(20) NOT NULL,
`number` varchar(255) DEFAULT NULL,
`profile_id` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
--
-- Indexes for table `phonenumber`
--
ALTER TABLE `phonenumber`
ADD PRIMARY KEY (`id`),
ADD KEY `FK8sfxu3ejjpklkd3njt3767ape` (`profile_id`);
--
-- Constraints for table `phonenumber`
--
ALTER TABLE `phonenumber`
ADD CONSTRAINT `FK8sfxu3ejjpklkd3njt3767ape` FOREIGN KEY (`profile_id`) REFERENCES `profilebasic` (`profile_id`);
I have other tables and have made several views from those tables, where some cases profile_id is the Primary Key on those view. I have done successfully one to many association from views, where primary key is profile_id. But i have a view, where profile_id is not PK, as a result in time of fetching, it is generating right query but with wrong value.
Hibernate: select phone_numb0_.profile_id as profile_3_18_0_, phone_numb0_.id as id1_18_0_, phone_numb0_.id as id1_18_1_, phone_numb0_.number as number2_18_1_ from PhoneNumber phone_numb0_ where phone_numb0_.profile_id=?
2020-08-23 04:00:48.396 TRACE 9292 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [21451]
Here parameter [1] as [BIGINT] - [21451] is the wrong value : PK of the view, where right value will be 1134. But as i told earlier, this is working where the primary key of the view is profile_id.
I have seen several questions in stackoverflow. Now i want to know : is there any way by which i can associate the phone number by one-to-many, where profile_id is not PK. If it is not possible, i have to read the phone number for each row of the views.
View Entity :
#Entity
#Table(name = "donner_assing_show")
public class DonnerAssingShow implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "donner_assingment_id")
private long donnerAssingmentId;
#Size(max = 255)
#Column(name = "agent_id")
private String agentId;
#Size(max = 255)
#Column(name = "donner_id")
private String donnerId;
#Size(max = 255)
#Column(name = "assing_date")
private String assingDate;
#Lob
#Size(max = 2147483647)
#Column(name = "assing_note")
private String assingNote;
#Size(max = 255)
#Column(name = "need_date")
private String needDate;
#Size(max = 255)
#Column(name = "post_id")
private String postId;
#Size(max = 255)
#Column(name = "blood_manage_status")
private String bloodManageStatus;
#Basic(optional = false)
#NotNull
#Column(name = "profile_id")
private long profileId;
#Size(max = 255)
#Column(name = "available")
private String available;
#Size(max = 255)
#Column(name = "birth_date")
private String birthDate;
#Size(max = 255)
#Column(name = "blood_Group")
private String bloodGroup;
#Size(max = 255)
#Column(name = "care_of")
private String careOf;
// #Pattern(regexp="[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", message="Invalid email")//if the field contains email address consider using this annotation to enforce field validation
#Size(max = 255)
#Column(name = "email")
private String email;
#Size(max = 255)
#Column(name = "gender")
private String gender;
#Size(max = 255)
#Column(name = "marital_status")
private String maritalStatus;
#Size(max = 255)
#Column(name = "name")
private String name;
#Size(max = 255)
#Column(name = "profession")
private String profession;
#Size(max = 255)
#Column(name = "religion")
private String religion;
#Size(max = 255)
#Column(name = "user_id")
private String userId;
#OneToMany
// #OneToMany(fetch = FetchType.EAGER)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "profile_id")
private List<PhoneNumber> phone_number;
// #OneToMany
#OneToMany(fetch = FetchType.EAGER)
// #LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "profile_id")
private List<Address> addressList;
// constructor-getter/setters
}
GitHub Link of the project where stacked
Unit Test of the code
Dump Data of the project
The join column is not required to be part of the primary key, or using the same column name in both tables. You can specify which column you want to join by using "referenceColumnName".
#Entity
public class ProfileBasic
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "profile_id")
private Long id;
//....some columns.
#OneToMany(cascade = CascadeType.ALL, mappedBy = "profile", orphanRemoval = true)
private List<PhoneNumber> phone_number;
// getters-setters
}
public class PhoneNumber implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "profile_id")
#JoinColumn(name = "profile_id", referencedColumnName = "profile_id", nullable = false)
private ProfileBasic profile;
// getters-setters
}
You should correct your mapping in this way:
#Entity
public class ProfileBasic {
//....some columns.
#OneToMany(cascade = CascadeType.ALL, mappedBy = "profile", orphanRemoval = true)
private List<PhoneNumber> phoneNumbers;
}
#Entity
public class PhoneNumber implements Serializable {
// getters-setters and other columns
#ManyToOne
#JoinColumn(name = "profile_id", nullable = false)
private ProfileBasic profile;
}
Comments:
The #JoinColumn annotation should be used on side that owns foreign key column (this is PhoneNumber in your case).
It is not necessary to use the referencedColumnName when the FK refereed to the PK column.
It is good to follow java naming conventions. So, it is better to use phoneNumbers as a property name instead of phone_number.
I try load roles of the user with User entity via #ManyToMany for auth, but I get Exception.
ERROR: column user0_.role_id does not exist
I just started to learn spring data. Please help me fix this issue. Thank You.
User entity:
#Entity(name = "users")
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "role_id")
private int roleId;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_id_role_id"
,
joinColumns = {
#JoinColumn(name = "user_id",
nullable = false,
updatable = false)
},
inverseJoinColumns = {
#JoinColumn(name = "role_id",
nullable = false,
updatable = false)
}
)
private List<Role> authorities;
#Column(name = "account_non_expired")
private boolean accountNonExpired;
#Column(name = "account_non_locked")
private boolean accountNonLocked;
#Column(name = "credentials_non_expired")
private boolean credentialsNonExpired;
#Column(name = "enabled")
private boolean enabled;
...
And Role entity:
#Entity(name = "user_role")
public class Role implements GrantedAuthority {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "role")
private String role;
...
My PostgreSQL schema:
CREATE TABLE IF NOT EXISTS user_role (
id SERIAL PRIMARY KEY,
role VARCHAR(10) NOT NULL
);
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(20) UNIQUE NOT NULL,
password VARCHAR(20) UNIQUE NOT NULL,
account_non_expired BOOLEAN NOT NULL DEFAULT TRUE,
account_non_locked BOOLEAN NOT NULL DEFAULT TRUE,
credentials_non_expired BOOLEAN NOT NULL DEFAULT TRUE,
enabled BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE user_id_role_id (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
FOREIGN KEY (role_id) REFERENCES user_role (id),
FOREIGN KEY (user_id) REFERENCES users (id)
);
Error indicates that you don't have property role_id in table user.
You have this property in entity:
#Column(name = "role_id")
private int roleId;
but when you create table:
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(20) UNIQUE NOT NULL,
password VARCHAR(20) UNIQUE NOT NULL,
account_non_expired BOOLEAN NOT NULL DEFAULT TRUE,
account_non_locked BOOLEAN NOT NULL DEFAULT TRUE,
credentials_non_expired BOOLEAN NOT NULL DEFAULT TRUE,
enabled BOOLEAN NOT NULL DEFAULT TRUE
);
you don't have property role_id.
Just remove property roleId from User entity and it should work :)
I am attempting to build a permission rule for accessing items (stored in table tab) by the users (stored in table users). The availability whether to see something is stored in permissions_tabs table.
The result should be as if the statement is run (for the user with id=2):
SELECT project.tab.tab_id, project.tab.parent, project.tab.name
FROM project.tab
INNER JOIN project.permissions_tabs
ON project.tab.tab_id=project.permissions_tabs.tab_id
WHERE permissions_tabs.user_id=2 AND permissions_tabs.view=true;
I am attempting to do it via the #OneToMany annotation, but it fails - I receive the message:
Deployment failed. The message was: Exception Description: The
#JoinColumns on the annotated element [field allowedTabs] from the
entity class [class
com.jtsmr.scheduler.persistence.entities.UsersEntity] is incomplete.
When the source entity class uses a composite primary key, a
#JoinColumn must be specified for each join column using the
#JoinColumns. Both the name and the referencedColumnName elements must
be specified in each such #JoinColumn.
Before amending the DB and adding the permissions_tab, it all worked. When I added it - I was unable to write the correct #OneToMany annotation, neither I succeeded in finding manual for my case.
If it is not possible, a solution via JPQL will suffice (I was unable to construct a join operation).
Here is my setup:
DB (creation statements for easier understanding):
CREATE TABLE `users` (
`user_id` bigint(20) NOT NULL,
`username` varchar(45) NOT NULL,
`password` varchar(45) NOT NULL,
`name` varchar(45) NOT NULL,
`email` varchar(80) NOT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `user_id_UNIQUE` (`user_id`),
UNIQUE KEY `username_UNIQUE` (`username`)
);
CREATE TABLE `tab` (
`tab_id` bigint(20) NOT NULL AUTO_INCREMENT,
`parent` bigint(20) NOT NULL DEFAULT '0',
`name` varchar(45) NOT NULL,
PRIMARY KEY (`tab_id`)
);
CREATE TABLE `permissions_tabs` (
`user_id` bigint(20) NOT NULL,
`tab_id` bigint(20) NOT NULL,
`to_view` bit(1) DEFAULT b'0',
`to_edit` bit(1) DEFAULT b'0',
PRIMARY KEY (`user_id`,`tab_id`),
KEY `user_id_idx` (`user_id`),
KEY `tab_id_idx` (`tab_id`),
CONSTRAINT `tab_id` FOREIGN KEY (`tab_id`) REFERENCES `tab` (`tab_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
);
The JPA Mapping is as follows (functions omitted):
Tabs:
#Entity
#Table(name = "tab")
public class TabEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "tab_id", insertable = false, updatable = false)
private Long tabId;
#Column(name = "parent", insertable = false, updatable = false)
private Long parent;
#Column(name = "name", insertable = false, updatable = false)
private String name;
}
Users:
#Entity
#Table(name = "users")
public class UsersEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long userId;
#Column(name = "username")
private String username;
#Column(name = "password")
private String password;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#OneToMany
#JoinTable(
name = "permissions_tabs",
joinColumns
= {
#JoinColumn(name = "user_id", referencedColumnName = "user_id")},
inverseJoinColumns
= {
#JoinColumn(name = "tab_id", referencedColumnName = "tab_id")})
private List<PermissionsTabsEntity> allowedTabs;
}
Primary key for the PermissionsTabsEntity class:
#Embeddable
public class PermissionsTabsPK implements Serializable {
#Column(name = "user_id")
private Long userId;
#Column(name = "tab_id")
private Long tabId;
}
And the PermissionsTabsEntity:
#Entity
#Table(name = "permissions_tabs")
public class PermissionsTabsEntity implements Serializable {
#EmbeddedId
private PermissionsTabsPK id;
#Column(name = "to_view")
private boolean toView;
#Column(name = "to_edit")
private boolean toEdit;
}
Thank you in advance!
Basically I want to refer the start city and end city to Bay object. Which is already created.
Table got crated .....But I can find only one bay field. And while saving
19:14:52,723 DEBUG [SqlExceptionHelper] could not execute statement [n/a]
org.postgresql.util.PSQLException: ERROR: null value in column "bay" violates not-null constraint
Detail: Failing row contains (3, 0, null, null, null, null).
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2270)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1998)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:255)
at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:570)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:420)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:366)
at org.apache.commons.dbcp.DelegatingPreparedStatement.executeUpdate(DelegatingPreparedStatement.java:102)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:186)
Generated Table :
CREATE TABLE iot.vehicle
(
vehicle_id integer NOT NULL,
cost bigint,
endtime timestamp without time zone,
satrttime timestamp without time zone,
vehicletype integer,
bay integer NOT NULL,enter code here
CONSTRAINT vehicle_pkey PRIMARY KEY (vehicle_id),
CONSTRAINT fk_1w93t3827hqk1dji0s585ocf1 FOREIGN KEY (bay)
REFERENCES iot.bay (bay_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
}
public class Vehicle {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "VEHICLE_ID")
private int vehicleId;
#ManyToOne(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="BAY", nullable = false, insertable = false, updatable = false)
private Bay startCity;
#ManyToOne(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="BAY", nullable = false, insertable = false, updatable = false)
private Bay endCity;
#Column(name = "SATRTTIME")
private Date startTime;
#Column(name = "ENDTIME")
private Date endTime;
#Column(name = "COST")
private long cost;
#Column(name = "VEHICLETYPE")
private TranportType vehicleType;
}
public class Bay {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name = "BAY_ID")
private int bayId;
#Column(name = "COUNTRY", nullable = false)
String country;
#Column(name = "CITINAME", nullable = false)
String citiName;
#Column(name = "BAYTYPE")
TranportType bayType;
//getters/setters
}
When you persist Vehicle, you obviously don't set the bay value (startCity and endCity).
And don't give them the same name:
#JoinColumn(name="...") //<- change name attribute here
I use Spring Data JPA (version is 1.7.2.RELEASE) and MySQL 5.5.
Here is the simplified version of my tables:
CREATE TABLE `station` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1$$
CREATE TABLE `ticket` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`trip_id` bigint(20) unsigned NOT NULL,
`wagon_number` tinyint(4) unsigned NOT NULL,
`place_number` tinyint(4) unsigned NOT NULL,
`departure_station_id` bigint(20) unsigned NOT NULL,
`arrival_station_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `departure_trip_station_fk_idx` (`trip_id`,`departure_station_id`),
KEY `arrival_trip_station_fk_idx` (`trip_id`,`arrival_station_id`),
CONSTRAINT `arrival_trip_station_fk` FOREIGN KEY (`trip_id`, `arrival_station_id`) REFERENCES `trip_station` (`trip_id`, `station_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `departure_trip_station_fk` FOREIGN KEY (`trip_id`, `departure_station_id`) REFERENCES `trip_station` (`trip_id`, `station_id`),
) ENGINE=InnoDB DEFAULT CHARSET=latin1$$
CREATE TABLE `train` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`number` int(11) unsigned NOT NULL,
`name` varchar(64) NOT NULL,
`enabled` bit(1) NOT NULL DEFAULT b'1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1$$
CREATE TABLE `train_station` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`train_id` bigint(20) unsigned NOT NULL,
`station_id` bigint(20) unsigned NOT NULL,
`departure_time` mediumint(8) unsigned NOT NULL,
`arrival_time` mediumint(8) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `train_station_uq` (`train_id`,`station_id`),
KEY `train_id_idx` (`train_id`),
KEY `id_idx` (`station_id`),
KEY `station_fk_idx` (`station_id`),
CONSTRAINT `station_fk` FOREIGN KEY (`station_id`) REFERENCES `station` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `train_fk` FOREIGN KEY (`train_id`) REFERENCES `train` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1$$
CREATE TABLE `trip` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`train_id` bigint(20) unsigned NOT NULL,
`departure_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `train_id` (`train_id`),
CONSTRAINT `timetable_fk` FOREIGN KEY (`train_id`) REFERENCES `train` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=246 DEFAULT CHARSET=latin1$$
CREATE TABLE `trip_station` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`train_id` bigint(20) unsigned NOT NULL,
`station_id` bigint(20) unsigned NOT NULL,
`trip_id` bigint(20) unsigned NOT NULL,
`arrival_date` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`departure_date` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
UNIQUE KEY `trip_station_uq` (`trip_id`,`station_id`),
KEY `trip_fk_idx` (`trip_id`),
KEY `train_station_fk_idx` (`train_id`,`station_id`),
CONSTRAINT `train_station_fk` FOREIGN KEY (`train_id`, `station_id`) REFERENCES `train_station` (`train_id`, `station_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `trip_fk` FOREIGN KEY (`trip_id`) REFERENCES `trip` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1$$
Here are my entities:
#Entity
#Getter
#Setter
public class Station {
#Id
private long id;
#Basic
private String name;
#OneToMany(mappedBy = "station", fetch = FetchType.LAZY)
private Collection<TrainStation> stationTrains;
}
#Entity
#Getter
#Setter
public class Ticket {
#Id
private long id;
#Basic
#Column(name = "place_number")
private byte placeNumber;
#Basic
#Column(name = "wagon_number")
private byte wagonNumber;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "trip_id", referencedColumnName = "trip_id", nullable = false),
#JoinColumn(name = "departure_station_id", referencedColumnName = "station_id", nullable = false)
})
private TripStation departureTripStation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "trip_id", referencedColumnName = "trip_id", nullable = false),
#JoinColumn(name = "arrival_station_id", referencedColumnName = "station_id", nullable = false)
})
private TripStation arrivalTripStation;
}
#Entity
#Getter
#Setter
public class Train {
#Id
private long id;
#Basic
private int number;
#Basic
private String name;
#Basic
#Column(name = "enabled", columnDefinition = "BIT", length = 1)
private boolean enabled;
#OneToMany(mappedBy = "train", fetch = FetchType.LAZY)
private List<TrainStation> trainStations;
#OneToMany(mappedBy = "train", fetch = FetchType.LAZY)
private List<Trip> trips;
}
#Entity
#Table(name = "train_station", schema = "", catalog = "rail_db",
uniqueConstraints = #UniqueConstraint(columnNames = { "train_id", "station_id" })
)
#Getter
#Setter
public class TrainStation {
#Id
private long id;
#Basic
#Column(name = "departure_time")
private int departureTime;
#Basic
#Column(name = "arrival_time")
private int arrivalTime;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "train_id", referencedColumnName = "id", nullable = false)
private Train train;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "station_id", referencedColumnName = "id", nullable = false)
private Station station;
#OneToMany(mappedBy = "trainStation", fetch = FetchType.LAZY)
private Collection<TripStation> tripStations;
}
#Entity
#Getter
#Setter
public class Trip {
#Id
private long id;
#Basic
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#Column(name = "departure_date")
private DateTime departureDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "train_id", referencedColumnName = "id", nullable = false)
private Train train;
#OneToMany(mappedBy = "trip", fetch = FetchType.LAZY)
private Collection<TripStation> tripStations;
}
#Entity
#Table(name = "trip_station", schema = "", catalog = "rail_db",
uniqueConstraints = #UniqueConstraint(columnNames = { "trip_id", "station_id" })
)
#Getter
#Setter
public class TripStation {
#Id
private long id;
#Basic
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#Column(name = "arrival_date")
private DateTime arrivalDate;
#Basic
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#Column(name = "departure_date")
private DateTime departureDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "trip_id", referencedColumnName = "id", nullable = false)
private Trip trip;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "train_id", referencedColumnName = "train_id", nullable = false),
#JoinColumn(name = "station_id", referencedColumnName = "station_id", nullable = false)
})
private TrainStation trainStation;
#OneToMany(mappedBy = "departureTripStation", fetch = FetchType.LAZY)
private Collection<Ticket> departureTickets;
#OneToMany(mappedBy = "arrivalTripStation", fetch = FetchType.LAZY)
private Collection<Ticket> arrivalTickets;
}
As you can see, my tables contain the surrogate primary keys and the composite natural keys (e.g. trip_station has UNIQUE KEY trip_station_uq (trip_id,station_id)).
For relationships between entities I offen use composite natural keys because they reflect the logic of domain.
But then I get this runtime exception:
ERROR o.s.web.context.ContextLoader - Context initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [com/xxx/rail/config/JpaConfig.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build EntityManagerFactory
...
Caused by: org.hibernate.AnnotationException: referencedColumnNames(trip_id, station_id) of com.xxx.rail.domain.entity.Ticket.arrivalTripStation referencing com.xxx.rail.domain.entity.TripStation not mapped to a single property
...
I want to understand what is wrong?
Is it possible to use natural composite keys (which are not primary keys) for relationships when surrogate primary keys exist?