I have two entities. I want to cascade the insertion of the child entity when the owner entity is persisted and set the SSO_ID of the child entity to the one that was generated for the owner by the generator.
#Entity(name = "USERS")
#Table(name = "USERS")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_GENERATOR")
#SequenceGenerator(name = "ID_GENERATOR", sequenceName = "ID_SEQUENCE")
#Column(name = "SSO_ID")
private Long ssoId;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserEmail> userEmails = new ArrayList<>();
// getters, setters etc.
}
#Entity(name = "USER_EMAILS")
#Table(name = "USER_EMAILS")
#IdClass(UserEmailId.class)
public class UserEmail {
#Id
#Column(name = "SSO_ID")
private Long ssoId;
#Id
#Column(name = "USER_MAIL")
private String userMail;
#Id
#Column(name = "START_DATE")
private Date startDate;
#Id
#Column(name = "EMAIL_TYPE")
private String emailType;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
// getters, setters etc.
}
The UserEmail ID class is:
public class UserEmailId implements Serializable {
private Long ssoId;
private String userMail;
private Date startDate;
private String emailType;
// getters, setters etc.
}
Instead, I get an error:
insert into hub_users_emails (user_sso_id, email_type, sso_id, start_date, user_mail) values (?, ?, ?, ?, ?)
(etc.)
binding parameter [1] as [BIGINT] - [1234837655] => this is user_sso_id
(etc.)
binding parameter [3] as [BIGINT] - [null] => this is the original sso_id
SQL Error: 904, SQLState: 42000
o.h.engine.jdbc.spi.SqlExceptionHelper : ORA-00904: "USER_SSO_ID": invalid identifier
I've tried some other setups of one to many (bidirectional, unidirectional, etc.) but It seems that this problem persists between all implementations.
Help is appreciated.
As you use #ManyToOne and #OneToMany, hibernate will create user_sso_id on your USER_EMAILS table. I am not sure why do you want another ssoId on USER_EMAILS.
I have removed sso_id from USER_EMAILS and now it's working fine. I know this is not the exact answer of your question. Following code may help you.
#Entity(name = "USERS")
#Table(name = "USERS")
#Setter
#Getter
public class User {
#Id
#SequenceGenerator(name = "ID_GENERATOR", sequenceName = "ID_SEQUENCE")
#GeneratedValue(generator = "ID_GENERATOR" )
#Column(name = "SSO_ID")
private Long ssoId;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "user")
private List<UserEmail> userEmails = new ArrayList<>();
}
#Setter
#Getter
#Entity(name = "USER_EMAILS")
#Table(name = "USER_EMAILS")
#IdClass(UserEmailId.class)
public class UserEmail {
#Id
#Column(name = "USER_MAIL")
private String userMail;
#Id
#Column(name = "START_DATE")
private Date startDate;
#Id
#Column(name = "EMAIL_TYPE")
private String emailType;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
}
#Setter
#Getter
public class UserEmailId implements Serializable {
private String userMail;
private Date startDate;
private String emailType;
}
public class SomeClass{
public User saveUser(){
User user = new User();
UserEmail userEmail = new UserEmail();
userEmail.setUser(user);
userEmail.setEmailType("type");
userEmail.setStartDate(new Date());
userEmail.setUserMail("someEmail#gmail.com");
user.setUserEmails(Arrays.asList(userEmail));
userRepo.save(user);
}
}
Related
I am trying to get the latest Flight on a specific airport of an Aircraft with Spring JPA query builder. The Aircraft data is managed in a different application.
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(schema = "schema1")
public class Flight implements Serializable {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "flight_sequence"
)
#SequenceGenerator(
name = "flight_sequence",
allocationSize = 1
)
#Column(nullable = false, updatable = false)
private Long id;
private Aircraft aircraft;
private Date date;
private String origin;
private String destination;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(schema = "schema2")
public class Aircraft implements Serializable {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "aircraft_sequence"
)
#SequenceGenerator(
name = "aircraft_sequence",
allocationSize = 1
)
#Column(nullable = false, updatable = false)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="operator_id", nullable=false)
private Operator operator;
private String registration;
private String acType;
private Date createdAt;
private Date updatedAt;
}
public interface FlightRepository extends JpaRepository<Flight, Long> {
Flight findFirstByDestinationAndAircraftRegistrationOrderByDateDesc(String destination, String registration);
}
But it throws this exception:
org.hibernate.query.criteria.internal.BasicPathUsageException: Cannot join to attribute of basic type
You need to define association mapping for the Aircraft attribute of the Flight entity. Probably ManyToOne as you did for the Operator attribute of the Aircraft entity.
I am trying to make an one to many relationship between two tables, one of the two is the Aggregate root table (Vessel). But when I am creating a new entity the many to one table doesnt map its foreign key to the one to many table's primary key. What I am doing wrong?
Vessel Table (One to many)
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "vessel_info")
#SequenceGenerator(name = "vessel_id_seq",sequenceName = "vessel_id_seq", initialValue = 1,allocationSize = 1)
public class Vessel {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator ="vessel_id_seq" )
#Column(name = "vessel_code")
private Long vesselCode;
private String name;
private Long companyId;
private Long imo;
private String type;
#Column(name = "fleet_id")
private Long fleetId;
private String yard;
private Integer hn;
private Date delivered;
private Double age;
#OneToMany(mappedBy = "vessel",cascade = CascadeType.ALL,
targetEntity = BoilerInfo.class, orphanRemoval = true,fetch = FetchType.EAGER)
private List<BoilerInfo> boilerInfo ;
BoilerInfo Table (Many to one)
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "boiler_info")
#SequenceGenerator(name = "boiler_id_seq",sequenceName = "boiler_id_seq", initialValue = 1,allocationSize = 1)
public class BoilerInfo {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator ="boiler_id_seq" )
private Long id;
private String maker;
private String type;
private String tubeType;
private String exhGasByPass;
#ManyToOne(fetch = FetchType.EAGER, optional = false,targetEntity = Vessel.class)
#JoinColumn(name="vessel_code",nullable = false)
private Vessel vessel;
}
CREATE VESSEL
private final VesselRepository vesselRepository ;
#Override
public Vessel create(Vessel entity) {
log.info("Creating {}.", entity);
Vessel vessel = vesselRepository.save(entity);
return vessel;
}
JSON POST
{
"fleetId":"1",
"name":"BOILERTEST",
"type":"temp",
"companyId":"1",
"boilerInfo":[{
"maker": "temp",
"type":"temp"
}]
}
The logs from the SQL query
Set both side of relation:
vesel.getBoilerinfos().add(boilerInfo);
boilerInfo.setVesel(vesel);
I'm trying to implement soft delete between a parent and child entity in such a way that deleting the parent entity would delete all its child entities as well.
My parent entity is User with a #OneToMany relation with Profile. Profile is created with a reference to User. Deleting the User works fine but when trying to retrieve the Profile that was referencing the User, I get an exception.
{
"title": "Internal Server Error",
"status": 500,
"detail": "Unable to find com.user.domain.User with id 951; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.user.domain.User with id 951",
"path": "/api/profiles",
"message": "error.http.500"
}
User.java
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#EqualsAndHashCode(exclude = {"users"})
#ToString(exclude = {"users"})
#Getter
#JsonDeserialize(builder = Profile.Builder.class)
#Entity
#Table(name = "users")
#SQLDelete(sql="Update users SET deleted = 'true' where id=?")
#Where(clause="deleted != 'true'")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "password")
private final String password;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Column(name = "email")
private String email;
#Enumerated(EnumType.STRING)
#Column(name = "gender")
private Gender gender;
#OneToMany(mappedBy = "users", cascade = { CascadeType.PERSIST, CascadeType.MERGE }, orphanRemoval = true)
private Set<Profile> profiles = new HashSet<>();
#Column(name="deleted")
String deleteFlag;
}
Profile.java
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#EqualsAndHashCode(exclude = {"users"})
#ToString(exclude = {"users"})
#Getter
#JsonDeserialize(builder = Profile.Builder.class)
#Entity
#Table(name = "profile")
#SQLDelete(sql="Update users SET deleted = 'true' where id=?")
#Where(clause="deleted != 'true'")
public class Profile implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private final Long id;
#Column(name = "profile_name")
private final String profileName;
#Column(name = "date_of_birth")
private final LocalDate dateOfBirth;
#Column(name = "health_history")
private final String healthHistory;
#ManyToOne
#JoinColumn(name = "users_id")
#JsonIgnoreProperties("reports")
private final User users;
#Column(name="deleted")
String deleteFlag;
}
There is a problem in delete query for profile
#SQLDelete(sql="Update users SET deleted = 'true' where id=?")
Here you're updating users table. I think it's a problem why your profiles are not removed after deleting User.
I'm trying to build a multi-language database, so I've used this database design as a approach for mine.
Now I've two problems/questions:
I want to retrieve all LocalizedEvent for a given language and given categoryId. How can I make a inner join over the LocalizedCategory table with Hibernate Criteria API?
With SQL I would make this statement to get all LocalizedEvent + LocalizedCategory:
SELECT * FROM event e
INNER JOIN
localized_event le ON (le.event_id = e.event_id)
INNER JOIN
localized_category lc ON (lc.category_id = e.category_id)
WHERE
le.locale = 'de' AND lc.locale = 'de'
My current approach looks like this without getting the LocalizedCategory (with Criteria API):
Criteria c = session.createCriteria(LocalizedEvent.class, "localizedEvent");
c.createAlias("localizedEvent.event", "event");
c.createAlias("event.category", "category");
c.add(Restrictions.eq("category.categoryId", categoryId));
c.add(Restrictions.eq("localizedEvent.locale", language));
I think my mapping is not 100% correct. The entity LocalizedEvent should have a property localizedCategory, but I don't want to save the ID of this localizedCategory (therefore I'm using the #Transient annotation) in the LocalizedEvent table, e.g. using a ManyToOne relation (joining LOC_CATEGORY_ID). But I think it's not possible to do this, isn't it? I would have to map this transient field to LocalizedEvent "manually", because Hibernate is not supporting this mapping (if I'm right).
(Using JDBC this property/mapping would cause no problems, because I can easily make my inner joins and assign the property localizedCategory to the LocalizedEvent in a RowMapper or so).
My entities looks like this:
Event
#Entity
#Table(name = "EVENT")
public class Event {
#Id
#GeneratedValue
#Column(name = "EVENT_ID", unique = true)
private Long eventId;
#Column(name = "DATE")
private Date date;
#OneToMany(mappedBy = "event")
private Set<LocalizedEvent> localizedEvents;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
}
LocalizedEvent
#Entity
#Table(name = "LOCALIZED_EVENT")
public class LocalizedEvent {
#Id
#GeneratedValue
#Column(name = "LOC_EVENT_ID")
private Long locEventId;
#ManyToOne
#JoinColumn(name = "EVENT_ID")
private Event event;
#Transient
private LocalizedCategory localizedCategory;
#Column(name = "DESCRIPTION")
private String description;
#Column(name = "LOCALE")
private String locale;
}
Category
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID", unique = true)
private Long categoryId;
#OneToMany(mappedBy = "category")
private Set<LocalizedCategory> localizedCategories;
#OneToMany(mappedBy = "category")
private Set<Event> events;
}
LocalizedCategory
#Entity
#Table(name = "LOCALIZED_CATEGORY")
public class LocalizedCategory {
#Id
#GeneratedValue
#Column(name = "LOC_CATEGORY_ID")
private Long locCategoryId;
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
#Column(name = "NAME")
private String name;
#Column(name = "LOCALE")
private String locale;
}
I've read examples but have my personal question to you.
I have 2 tables:
Role:
id, name
User:
id, login, name, role_id
Role entity
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
User entity
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
And repository:
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "from User u left join Role r on u.role_id=r.id";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
This code is showing: Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Path expected for join! [from com.example.jpa.model.User u left join Role r on u.role_id=r.id]
How I understand entity can't contains 'id' (in my case in Role) for references and I should remove this field. But entity should have '#Id'.
In this case I should create new column in 'Role'? or I can use more beautiful decision?
I put all project to bb
To use join in HQL (JPQL) you don't need on clause
String Q_GET_ALL_USERS = "select u from User u left join u.role";
This query doesn't have any sence because of you don't use role in the where clause.
If you want to get users with a fetched role you can use join fetch
String Q_GET_ALL_USERS = "select u from User u left join fetch u.role";
Update
Your schema for User and Role is not commonly used. I advice to you make #ManyToMany association from user to roles and remove any user association from the Role
#Entity
#Table(name = "user")
public class User {
#ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;
}
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
}
No, you should create a new column in User.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "role_id")
private Role role;
Thank you all for answers. Right entities and query below (plus tables schema).
Tables (queries)
CREATE TABLE role (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(45) NOT NULL
);
CREATE TABLE user (
id INT NOT NULL PRIMARY KEY IDENTITY,
login VARCHAR(45) NOT NULL,
user_name VARCHAR(45) NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (role_id) REFERENCES role (id)
);
Entities:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
}
and
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
}
Repository
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "select u from User u left join u.role";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
}
#v-ladynev proposed alternative decision(use only #ManyToMany in User). More details you can find in comments under this answer.
When I check this decision I will update this answer (I hope I don't forget it :-))
Models
#Entity
#Table(name = "sys_std_user")
public class StdUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "class_id")
public int classId;
#Column(name = "user_name")
public String userName;
}
#Entity
#Table(name = "sys_std_profile")
public class StdProfile {
#Id
#Column(name = "pro_id")
public int proId;
#Column(name = "full_name")
public String fullName;
}
Controllers
#PersistenceUnit
private EntityManagerFactory emf;
#GetMapping("/join")
public List actionJoinTable() {
EntityManager em = emf.createEntityManager();
List arr_cust = em
.createQuery("SELECT u.classId, u.userName, p.fullName FROM StdUser u, StdProfile p WHERE u.classId=p.proId")
.getResultList();
return arr_cust;
}
Result:
[
[
1,
"Ram",
"Ram Pukar Chaudhary"
],
[
2,
"Raja",
"Raja Kishor Shah"
]
]