I'm working on a project, and I encountered a problem with the JPA relationship. I've been advised on another thread to change a couple of things, however, I still can't get it to work properly.
I'm getting an exception and I know where the problem is but not sure how to solve it.
here is the User class:
#Entity
#Table(name = "user")
public class UserModel implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "username", nullable = false)
private String username;
#Column(name = "password", length = 500, nullable = false)
private String password;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Car> cars;
}
here is the Car class:
#Entity
#Table(name = "car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(length = 11)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "make", nullable = false)
private String make;
#Column(name = "model", nullable = false)
private String model;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "id") //here is where the exception throws (duplicated ID or com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'user_model_id' in 'field list' if I change that to user_model_id.
private UserModel userModel;
}
here is the service impl:
#Component
#Transactional(readOnly = true)
public class CarServiceImpl implements CarService {
#Inject
private CarRepository carRepository;
#Inject
private UserRepository userRepository;
#Override
#Transactional(readOnly = false)
public Car addCar(Long userId, Car car) {
User user = userRepository.findOne(userId);
user.getCars().add(car);
car.setUser(user);
carRepository.save(car);
return car;
}
Any help will be really much appreciated.
Thank you so much
I think mappedBy needs to point to the field name that owns the relation. In this case this is userModel so it should be
#OneToMany(mappedBy = "userModel", cascade = CascadeType.ALL)
If the relationship is bidirectional, the mappedBy element must be
used to specify the relationship field or property of the entity that
is the owner of the relationship. (from https://www.objectdb.com/api/java/jpa/OneToMany)
Also I think that your #JoinColumn annotation is wrong. It should specify the column used to join the related entity - so it cannot be ID but something like user_id
See https://www.objectdb.com/api/java/jpa/JoinColumn#
Also your ddl is wrong - this piece says that cars and users have the same id - which you do not want - your ddl is missing the actual column for the user foreign key
FOREIGN KEY (id)
REFERENCES game.user (id)
So if you changed JoinColumn to #JoinColumn(name = "user_id") your ddl for the foreign key must be.
CREATE TABLE game.car (
id INT(11) NOT NULL AUTO_INCREMENT COMMENT '',
make VARCHAR(15) NOT NULL COMMENT '',
model VARCHAR(15) NOT NULL COMMENT '',
PRIMARY KEY (id) COMMENT '',
user_id INT(11),
CONSTRAINT fk_user
FOREIGN KEY (user_id)
REFERENCES game.user (id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)
If I can add my 2 cents to the mapping - try to avoid bidirectional relations wherever you can.
Related
I have two tables:
#Entity
public class TestEntity {
#Id
#Column(name = "id")
private UUID id;
#OneToOne(targetEntity = InfoEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name="id", referencedColumnName = "id")
private InfoEntity info;
...
}
#Entity
public class InfoEntity {
#Id
#Column(name = "id")
private UUID id;
#OneToOne(mappedBy = "info")
private TestEntity test;
...
}
Basically I want to define a Foreign Key from TestEntity to InfoEntity by id fields which are the same in both tables. The problem is that when I check the database in IntelliJ Idea I don't see any Foreign Keys in keys section (checked both tables), only their PK's. Is something wrong with this code? I already set the property as it suggested in another similar question:
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
The problem is that name in JoinColumn has value id. Change it to info_id for example:
#OneToOne(targetEntity = InfoEntity.class, cascade = CascadeType.ALL)
#JoinColumn(name="info_id", referencedColumnName = "id")
private InfoEntity info;
You TestEntity already has a column id, so it has to has another name.
I have spring-boot application with this two JPA entities joined by one-to-many relation.
#Entity
#Table(name = "todos")
public class ToDo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#Column
private LocalDate targetDate;
public ToDo() {
}
// Constructors, getters, setter, etc.
}
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true, nullable = false)
private String username;
#Column(nullable = false)
private String password;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<ToDo> toDos = new ArrayList<>();
// Constructors, getters, setter, etc.
}
For such kind of relation I expect to have only two tables but Hibernate create three.
Hibernate: create table todos (id bigserial not null, description varchar(255), target_date date, user_id int8, primary key (id))
Hibernate: create table users (id bigserial not null, password varchar(255) not null, username varchar(255) not null, primary key (id))
Hibernate: create table users_to_dos (user_id int8 not null, to_dos_id int8 not null)
The last one look useless. Why it created and could I prevent that? May be something is wrong in code?
Since your toDos list on the user entity does not have a #JoinColumn annotation, hibernate assumes there's an additional mapping table for this and creates it.
Add the #JoinColumn annotation to the field and it will not be created
See more on this guide
#OneToMany(mappedBy="speciality",fetch = FetchType.LAZY)
#JsonIgnore
private Set<DepartmentMaster> departmentSpeciality;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="SPECALITY_ID")
#JsonIgnore
private SpecialityMaster speciality;
Try to do the mapping like this I don't think it should create any problem.
I have 2 tables, the first one is quite variable, the second one contains only constants:
USER.ID USER.NAME USER.USER_TYPE (FK on USER_TYPE.ID)
INT VARCHAR(64) INT(1)
----------------------------------
1 Alex 3
2 Jane 1
3 Carl 3
USER_TYPE.ID USER_TYPE.VALUE
INT(1) VARCHAR(64)
------------------------------
1 PENDING
2 REGISTERED
3 BANNED
4 ACTIVE
The foreign key USER.USER_TYPE is required and refering to a primary key USER_TYPE.ID in table USER_TYPE (one-to-one relation). Here is my mapping in Hibernate.
User.java
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE")
private UserType userType;
}
UserType.java
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "VALUE")
private String value;
}
My goal is to keep the enumerated values in the database. How to map UserType's value instead of id to User and validate it? I want to pass the constant VALUE to the String instead of its ID.
private String userType;
The expected result of the first user would be:
User[id=1, name=Alex, userType=Banned]
User[id=2, name=Jane, userType=Pending]
User[id=3, name=Carl, userType=Banned]
My attempt was to use this annotation on definition of table twice with both colums switched
#SecondaryTable(name="USER_TYPE",
pkJoinColumns={#PrimaryKeyJoinColumn(name="ID", referencedColumnName="USER_TYPE")}
)
and get the VALUE with
#Column(table="USER_TYPE", name="VALUE")
private String UserType;
however it leads to the error
Unable to find column with logical name: USER_TYPE in org.hibernate.mapping.Table(USER) and its related supertables and secondary tables
First you need to change the relation from #OneToOne to #ManyToOne as UserType can be used by one or many User and User can have one and one UserType.
Secondly use referencedColumnName which references :
The name of the column referenced by this foreign key column.
So User entity will be:
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "USER_TYPE", referencedColumnName = "VALUE")
private UserType userType;
}
In UserType you should apply a unique constraint using #NaturalId to value field + do not provide its setter, to prevent duplicate values as It may lead to inconsistency:
#Entity
#Table(name = "USER_TYPE")
public class UserType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#NaturalId
#Column(name = "VALUE")
private String value;
}
Hope it solves the issue!
Enumerations could be simpler:
enum UserType {
PENDING,
REGISTERED,
BANNED,
ACTIVE
}
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int id;
#Column(name = "NAME")
private String name;
#javax.persistence.Enumerated
private UserType userType;
}
If you really need separated table and #OneToOne relation, you can use #Formula from Hibernate:
#Formula("(select ut.value from user_type ut where ut.ID = USER_TYPE)")
private String userType;
For this really special requirement you could use SecondaryTable annotation.
That is, you don't need UserType entity, but declare attribute userType as String in User entity with column mapping to the secondary table "USER_TYPE".
First of all, I suggest you use ManyToOne relation. and Not CascadeType.ALL if you are not planning update or delete on USER_TYPE table.
If you do not need adding new UserTypes frequently use enum for it. It will just work as you want.
Second solution: As long as fetch = FetchType.EAGER you can add A transient field and return value of UserType in getter.
I'm using spring-boot 1.5.4 with spring-data-jpa and I'm trying to override the auto generated foreign key name during spring.jpa.hibernate.ddl-auto=create.
For simple id, I was able to override it: simple_fk
Hibernate: alter table my_entity add constraint simple_fk foreign key (simple_id) references simple
But not for foreign key with composite id: FKms12cl9ma3dk8egqok1dasnfq
Hibernate: alter table my_entity add constraint FKms12cl9ma3dk8egqok1dasnfq foreign key (composite_id1, composite_id2) references composite
What is wrong with my code? I also tried #PrimaryKeyJoinColumn.
Please see the class definitions below.
#Entity
public class Simple {
#Id
private long id;
}
#Entity
public class Composite {
#Id
private CompositeId id;
}
#Embeddable
public class CompositeId {
#Column
private long id1;
#Column
private long id2;
}
#Entity
public class MyEntity {
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "simple_fk"),
name = "simple_id", referencedColumnName = "id")
private Simple simple;
#ManyToOne
#JoinColumns(foreignKey = #ForeignKey(name = "composite_fk"), value = {
#JoinColumn(name = "composite_id1", referencedColumnName = "id1"),
#JoinColumn(name = "composite_id2", referencedColumnName = "id2")
})
private Composite composite;
}
This is a known issue with the Hibernate which was fix in version 5.2.8
So there are two ways to fix it: Either you update the Hibernate to the version 5.2.8 or up by adding
<hibernate.version>5.2.10.Final</hibernate.version>
to your pom.xml, which basically will update the Hibernate to the latest version.
or if Hibernate update is not possible or is just too risky you can add legacy/deprecated #org.hibernate.annotations.ForeignKey(name = "composite_fk") annotation on your
composite field which will make you code look like
#Entity
public class MyEntity {
#ManyToOne
#JoinColumn(foreignKey = #ForeignKey(name = "simple_fk"), name = "simple_id", referencedColumnName = "id")
private Simple simple;
#ManyToOne
#JoinColumns(foreignKey = #ForeignKey(name = "composite_fk"), value = {
#JoinColumn(name = "composite_id1", referencedColumnName = "id1"),
#JoinColumn(name = "composite_id2", referencedColumnName = "id2") })
#org.hibernate.annotations.ForeignKey(name = "composite_fk")
private Composite composite;
}
I'm using Hibernate and Oracle database and just got stuck.
I'm trying to map this table:
CREATE TABLE passengers_on_the_flight
(
flight_id NUMERIC(10) REFERENCES flight(flight_id),
passenger_id NUMERIC(20) REFERENCES passenger(passenger_id),
seat NUMERIC(5) NOT NULL,
CONSTRAINT "not free" PRIMARY KEY (flight_id,passenger_id,seat)
);
So my mapping class looks like:
#Entity
#Table(name = "PASSENGERS_ON_THE_FLIGHT")
#NamedQueries({
#NamedQuery(name = "PassengersOnTheFlight.findAll", query = "SELECT p FROM PassengersOnTheFlight p")})
public class PassengersOnTheFlight implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected PassengersOnTheFlightPK passengersOnTheFlightPK;
public PassengersOnTheFlightPK getPassengersOnTheFlightPK() {
return passengersOnTheFlightPK;
}
public void setPassengersOnTheFlightPK(PassengersOnTheFlightPK passengersOnTheFlightPK) {
this.passengersOnTheFlightPK = passengersOnTheFlightPK;
}
#JoinColumn(name = "SEAT", referencedColumnName = "SEAT", insertable = false, updatable = false)
private int seat;
#JoinColumn(name = "FLIGHT_ID", referencedColumnName = "FLIGHT_ID", insertable = false, updatable = false)
#ManyToOne
private Flight flight;
#JoinColumn(name = "PASSENGER_ID",referencedColumnName = "PASSENGER_ID",insertable = false, updatable = false)
#ManyToOne
private Passenger passenger;
//Getters, setters for seat, flight and passanger
And primary key class:
#Embeddable
public class PassengersOnTheFlightPK implements Serializable {
#Column(name = "FLIGHT_ID",nullable=false)
private long flightId;
#Column(name = "SEAT",nullable=false)
private int seat;
#Column(name = "PASSENGER_ID", nullable=false)
private Long passengerId;
//Getters and setters for seat, flightId and passangerId
I tried to persist something and got
ORA-00957: duplicate column name
That because Hibernate generates such query:
insert into PASSENGERS_ON_THE_FLIGHT (seat, FLIGHT_ID, PASSENGER_ID, SEAT) values (?, ?, ?, ?)
I don't get why. Did I mis something in the mapping classes?
I get the same problem with a Coposite Key and solved it adding this params at the #JoinColumn hibernate annotation in the get methods of the external PK duplicate entities:
#JoinColumn(..., updatable=false, insertable=false)
Hibernate really REALLY wants you to have a unique identity key for each table; it has "issues" without it. Try putting an identity key on your table.
You get your error because you specified seat twice. Once you have it as property of your entity PassengersOnTheFlight and second time in the key. If it is part of your key, remove it from the main object. If it is an integer, you probably don't want #JoinColumn for it anyway.