Hibernate generates wrong primary key - java

I have a Hibernate entity.
#AllArgsConstructor #NoArgsConstructor #Getter
#Entity
#Table(name = "app_category_link", schema = "mariott_application")
public class AppCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "app_category_link_id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "app_id")
private App app;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id")
private Category category;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id_who_added")
private User userWhoAdded;
#Column(name = "date_add")
private ZonedDateTime dateAdded;
}
And I have a #DataJpaTest that generated such DDL. Surprisingly it produdes an unexpected primary key.
create table mariott_application.app_category_link
(
app_category_link_id int8 not null,
date_add timestamp,
app_id bigserial not null,
category_id int8 not null,
user_id_who_added int8,
primary key (app_id, category_id) -- wrong
)
Why does Hibernate generate wrong primary key?

This can happen when you use the table name multiple times e.g. also in a #JoinTable/#CollectionTable. Always make use of an existing entity as inverse #OneToMany rather than defining a #ManyToMany association to avoid this issue.

Related

Java: Repeated column in mapping for collection

I am trying to create a OneToMany mapping with a LinkedHashMap for my main entity B, that contains the entities VC and P, but I am getting the following error:
Repeated column in mapping for collection:
com.test.model.B.pricing column: b_name
I could be wrong, but I believe that it has something to do with the #JoinColumns or #MapKeyJoinColumn annotation, as I have not done anything like this before, so I am quite sure that I am doing this part incorrectly.
My goal is that I should be able to provide the three fields:
b_name pc and c_id e.g. the VC/VCId
in order to get the a and d_a e.g. P.
Also, if there is a better way to structure things, then I am all ears, as I personally do not really like how I have set up my tables tbh (would be nice if I could just have the b and b_p tables, where the b_p could just have all five fields (key and value) from the p map).
Here is my main entity
#Setter
#Getter
#Entity
#Table(name = "b")
public class B implements Serializable {
#Id
#Column(nullable = false)
private String name;
#OneToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name = "b_p",
joinColumns = #JoinColumn(name = "b_name", referencedColumnName = "name"))
#MapKeyJoinColumns({
#MapKeyJoinColumn(name = "b_name"),
#MapKeyJoinColumn(name = "p_c"),
#MapKeyJoinColumn(name = "c_id")
})
private Map<VC, Price> pricing = new LinkedHashMap<>();
...
}
The Key to the map
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "v_c")
public class VC implements Serializable {
#EmbeddedId private VCId vcId;
}
The key's PK/Composite Key
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
public class VCId implements Serializable {
#Column(name = "b_name")
private String bName;
#Column(name = "p_c")
private SomeEnum pc;
#Column(name = "c_id")
private String cId;
}
The value for the map
#Setter
#Getter
#NoArgsConstructor
#Embeddable
#Entity
#Table(name = "price")
public class Price implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
#Column(name = "amount")
private BigDecimal amount;
#Column(name = "discount_amount")
private BigDecimal discountAmount;
}
DB tables
CREATE TABLE b
(
name VARCHAR(100) NOT NULL PRIMARY KEY
...
);
CREATE TABLE v_c
(
bundle_name VARCHAR(100) NOT NULL,
physical_currency TEXT NOT NULL,
coin_id VARCHAR(50) NOT NULL,
FOREIGN KEY (b_name) REFERENCES b (name) ON DELETE CASCADE,
PRIMARY KEY (b_name, p_c, c_id)
);
CREATE TABLE p
(
id BIGSERIAL NOT NULL PRIMARY KEY,
amount NUMERIC,
discount_amount NUMERIC DEFAULT 0.00
);
CREATE TABLE b_p
(
bname VARCHAR(100) NOT NULL,
p_c TEXT NOT NULL,
c_id VARCHAR(50) NOT NULL,
price_id BIGSERIAL NOT NULL,
FOREIGN KEY (b_name, p_c, c_id) REFERENCES v_c (b_name, p_c, c_id) ON DELETE CASCADE,
FOREIGN KEY (price_id) REFERENCES price (id) ON DELETE CASCADE,
PRIMARY KEY (b_name, p_c, c_id)
);
To prevent repeated mapping error, you just have to specify which join should update the column like such:
#OneToMany(cascade = CascadeType.PERSIST)
#JoinTable(name = "bundle_pricing",
joinColumns = #JoinColumn(name = "bundle_name",
referencedColumnName = "name"))
#MapKeyJoinColumns({
#MapKeyJoinColumn(name = "bundle_name", insertable = false, updatable = false),
#MapKeyJoinColumn(name = "physical_currency"), #MapKeyJoinColumn(name = "coin_id")})
private Map<VirtualCurrency, Price> pricing = new LinkedHashMap<>();
NOTE the insertable = false, updatable = false for bundle_name in the MapKeyJoinColumn
remove the #MapKeyJoinColumn(name = "bundle_name") from MapKeyJoinColumns, because when we are creating the JoinColumn in JoinTable it will create the column we don't need to mention it again.
#JoinTable(
name = "bundle_pricing",
joinColumns = #JoinColumn(name = "bundle_name", referencedColumnName = "name"))
#MapKeyJoinColumns({
#MapKeyJoinColumn(name = "physical_currency"),
#MapKeyJoinColumn(name = "coin_id")
})

Unexpected table created by ddl-auto update

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.

list only has one element

I'm using hibernate to read data from a database. I have 3 entities, sensor, zone and a N to N relationship between those two entities, that is the entity SensorZone.
For each entity I created a class, which are the examples that follow, they don't include a constructor, getters and setters:
#Entity
#Table(name = "Sensor")
public class Sensor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "zone")
private List<SensorZone> zones = new ArrayList<>();
}
#Entity
#Table(name = "Zone")
public class Zone {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "max_count")
private int maxCount;
#Column(name = "current_count")
private int currentCount;
#Column(name = "created")
private Timestamp created;
#Column(name = "modified")
private Timestamp modified;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "sensor")
private List<SensorZone> sensors = new ArrayList<>();
}
#Entity
#Table(name = "Sensor_Zone")
public class SensorZone {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "zone_id")
private Zone zone;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "sensor_id")
private Sensor sensor;
#Column(name = "enter_exit")
private boolean enterExit;
}
I'm using PostgreSQL as a database engine, and the tables are as follow:
CREATE TABLE Sensor (
id SERIAL,
name TEXT NOT NULL UNIQUE,
CONSTRAINT PK_Sensor PRIMARY KEY (id)
);
CREATE TABLE Zone (
id SERIAL,
name TEXT NOT NULL UNIQUE,
max_count INT NOT NULL,
current_count INT NOT NULL,
created TIMESTAMP NOT NULL,
modified TIMESTAMP NOT NULL,
CONSTRAINT PK_Zone PRIMARY KEY (id)
);
CREATE TABLE Sensor_Zone (
id SERIAL,
zone_id INT NOT NULL,
sensor_id INT NOT NULL,
enter_exit BOOLEAN NOT NULL,
CONSTRAINT PK_Zone_Sensor PRIMARY KEY (id, sensor_id, zone_id),
CONSTRAINT FK_Zone_Sensor_Zone FOREIGN KEY (zone_id) REFERENCES Zone (id),
CONSTRAINT FK_Zone_Sensor_Sensor FOREIGN KEY (sensor_id) REFERENCES Sensor (id)
);
And here's the values on the table Sensor_Zone:
The problem is that the field zones from Sensor only has one element in the list, even there are multiple elements in the database.
I've tried to put FecthType.EAGER, but it didn't change anything.
Not the exact solution (a.k.a. Could your model be re-modeled?)
I know it's easier said than done, but if you could avoid managing extra column(s) on the joining table, you could resolve it using standard example of many-to-many with joining table.
Perhaps there is a way of thinking about your extra column so that it could become owned by either Zone or Sensor entities? Maybe it's complementary as in on one side of many-to-many if it is set to TRUE it means one thing, but when it's missing it is equivalent to being FALSE.
#Entity
#Table(name = "Sensor")
public class Sensor {
// ...
#OneToMany
#JoinTable(
name = "Sensor_Zone",
joinColumns = { #JoinColumn(name = "sensor_id") },
inverseJoinColumns = { #JoinColumn(name = "zone_id") }
)
private List<Zone> zones = new ArrayList<>();
}
#Entity
#Table(name = "Zone")
public class Zone {
// ...
#OneToMany
#JoinTable(
name = "Sensor_Zone",
joinColumns = { #JoinColumn(name = "zone_id") },
inverseJoinColumns = { #JoinColumn(name = "sensor_id") }
)
private List<Sensor> sensors = new ArrayList<>();
}
And then your joining table does not need to be an entity simplifying things.
The Solution (Here be dragons)
Escape hatch: for solution please check Mykong's blog
You have to look into using #AssociationOverrides on the entity created from joining table. Please also note a separate class annotated with #Embeddable created to deal with the composite key in the joining table.

JPA Relationship issue

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.

Hibernate create foreign key to itself

I have a very strange problem with hibernate at the moment.
Somehow on a table, it create a foreign key which reference to itself. the column is also a primary key. This essentially prevent me from delete any row from that table.
In the log I could see a line:
DEBUG org.hibernate.SQL - alter table Device add index
FK79D00A76C682495E (id), add constraint FK79D00A76C682495E foreign key
(id) references Device (id)
Other table with similar table seems fine. and this seems to be true for both MySQL and Derby. I'm using hibernate 4.1.4
the annotated class is as follow.
#Entity(name = "Device")
public class Device extends DomainObject implements Searchable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected long id;
#Column(name = "Type")
#Enumerated(EnumType.STRING)
private DeviceTypeEnum type = DeviceTypeEnum.AccessControlDevice;
#Column(name = "Name", length = Constance.DATABASE_NAME_FIELD_LENGTH)
private String name;
#Column(name = "Description", length = Constance.DATABASE_DESCRIPTION_FIELD_LENGTH)
private String description;
#Column(name = "Identifier", length = Constance.DATABASE_IDENTIFIER_FIELD_LENGTH, unique = true)
private String identifier;
#ManyToMany
#JoinTable(name = "Device2Group", joinColumns = {#JoinColumn(name = "DeviceID")}, inverseJoinColumns = {#JoinColumn(name = "DeviceGroupID")})
private List<DeviceGroup> groups = new ArrayList<DeviceGroup>();
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "location")
private Location location;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "Address")
private Address address;
#ManyToOne
#JoinColumn(name = "Link", nullable = false)
private Link link;
}
It turns out that in one of the Entity Class "Location" which the Device entity references, it has a #ManyToMany mapped collection of Device, where it really should be #OneToMany.
After change the #ManyToMany to #OneToMany the constrain disappears.
I see no references to Device class in your code. So I am assuming that this class has been modified, but its table has not, because it has some data. (Why else should it have a foreign-key to itself?)
Try dropping this table in your database to make hibernate create it once more, or set p.hibernate.hbm2ddl.auto to create-drop.

Categories

Resources