I have a problem.
I am writing program which is connecting to database, where I cant change anything. I can only read data from it. So let's say I have three tables MOVIES, BOOKS, REVIEWDOCUMENT and two many to many tables MOVIES_REVIEWDOCUMENT plus BOOKS_REVIEWDOCUMENT.
Because I am using Spring Boot with Hibernate, I have written simple entities classes.
#Entity(name = "MOVIES")
#Table(name = "MOVIES")
public class Movies {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "MOVIE_KEY")
private String movieKey;
#Column(name = "TYPE_ANIMATED")
private String typeAnim;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "MOVIES_REVIEWDOCUMENTS",
joinColumns = #JoinColumn(name = "MOVIE_KEY"),
inverseJoinColumns = #JoinColumn(name = "DOCUMENT_KEY"))
private List<ReviewDocuments> reviewDocuments;
}
#Entity(name = "BOOKS")
#Table(name = "BOOKS")
public class Books {
#Id
#Column(name = "ID")
private Long id;
#Column(name = "BOOK_KEY")
private String bookKey;
#Column(name = "TYPE_WRITTEN")
private String typeWritten;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "BOOKS_REVIEWDOCUMENTS",
joinColumns = #JoinColumn(name = "BOOK_KEY"),
inverseJoinColumns = #JoinColumn(name = "DOCUMENT_KEY"))
private List<ReviewDocuments> reviewDocuments;
}
#Entity(name = "REVIEWDOCUMENTS")
#Table(name = "REVIEWDOCUMENTS")
public class ReviewDocuments {
#Id
#Column(name = "OBJID")
private Long objId;
#ManyToMany(mappedBy = "reviewDocuments")
private Set<Movies> movies;
#ManyToMany(mappedBy = "reviewDocuments")
private Set<Books> books;
}
And it is working pretty ok. But because as you can see MOVIES and BOOKS are almost indentical, only diffrence are those relational tables. I was wondering if I can somehow extract it to abstrac class.
Because what I need to do is to create service class which will iterate after books/movies and it's documents and do some operation on reviewDocuments. So easies way would be creating generic service for each of entities.
Example:
public void extractData() throws IOException {
Path tempDirectory = Files.createTempDirectory("zip_");
tempDirectory.toFile().deleteOnExit();
Set<Book> books = movieRepository.findByRidKey(extractionParameters.getWriteNumber());
for (Book book :
books) {
for (ReviewDocuments documents :
book.getReviewDocuments()) {
exportDataToFile(data);
}
directoryToZip(tempDirectory.toFile(), book.getId());
FileUtils.cleanDirectory(tempDirectory.toFile());
}
FileUtils.deleteDirectory(tempDirectory.toFile());
}
I don‘t think you can use inheritance with multiple many-to-many tables.
You could however define a common interface and implement your service based on that interface.
Related
I have two entities connected with many-to-many relationship. For example:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Game> games = new HashSet<>();
}
#Entity
public class Game {
#Id
private Long id;
#ManyToMany(mappedBy = "games", fetch = FetchType.LAZY)
List<Account> accounts = new ArrayList<>();
}
So, there is a table account_games(account_id, game_id) in mysql describing entities many-to-many relations.
I don't want to have Game entity anymore. Is there a way to get rid of Game and leave gameId relation only? So, I'd like to have code something like that:
#Entity
public class Account {
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "account_games",
joinColumns = {#JoinColumn(name="account_id")},
inverseJoinColumns = {#JoinColumn(name="game_id")}
)
private Set<Long> gameIds = new HashSet<>();
}
without making changes in database.
I've tried different configuration on javax.persistance annotations, but none worked
You can use #ElementCollection and #CollectionTable to achieve that.
#Entity
public class Account {
#Id
private Long id;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "account_games", joinColumns = #JoinColumn(name = "account_id"))
#Column(name = "game_id", nullable = false)
private Set<Long> gameIds = new HashSet<>();
}
You may have to change the query on how to filter data using gameId. Element Collection Query
I've faced with an issue, I've declared the bidirectional #ManyToMany relation between my entities, but when I try to perform the select by repository, the #ManyToMany collection is always empty.
There is an example of my entities:
#Data
#EqualsAndHashCode
#Entity
#Table(name = "provider", schema = "debt")
public class ProviderEntity {
#Id
#Column(name = "provider_id")
private String providerId;
#Column(name = "external_provider_id")
private String externalProviderId;
private String description;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "provider_supported_driver", schema = "debt",
joinColumns = #JoinColumn(name = "provider_id", foreignKey = #ForeignKey(name = "FK_PSD_TO_P")),
inverseJoinColumns = #JoinColumn(name = "driver_id", foreignKey = #ForeignKey(name = "FK_PSD_TO_D"))
)
private Set<DriverEntity> drivers = new HashSet<>();
}
#Data
#EqualsAndHashCode
#Entity
#Table(name = "driver", schema = "debt")
public class DriverEntity {
#Id
#Column(name = "driver_id")
private String driverId;
#Enumerated(EnumType.STRING)
private DriverType type;
private String description;
#ManyToMany(mappedBy = "drivers")
private Set<ProviderEntity> provider = new HashSet<>();
}
Also, I've enabled the SQL query logs, that hibernate generates, when I run it, the query works well.
I've tried different guides and answers for the same issues, but I've no result with them.
What's the reason for this behavior? Maybe has somebody any ideas?
I've found the reason for this issue and the problem is in the primary keys of my tables, they aren't generated by sequence or some different way, as I understand Hibernate doesn't understand how to map this columns
I got a lot of answers and advice, tried changing my code according to these tips.
But, unfortunately, these tips helped only partially.
Now, when creating a new project and creating a new user, I can add the desired user to the set of projects, and the required project will be added to the set of users.
But the relationship between the desired project and the desired user will not appear in the project_user table.
Please help find the answer.
Entity Project
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "project")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String description;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "project_users",
joinColumns = #JoinColumn(name = "project_id"),
inverseJoinColumns = #JoinColumn(name = "users_id"))
private Set<User> projectUserSet = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "project_task",
joinColumns = #JoinColumn(name = "project_id"),
inverseJoinColumns = #JoinColumn(name = "task_id"))
private Set<Task> projectTaskSet = new HashSet<>();
public void addUserToProject(User user){
this.projectUserSet.add(user);
user.getUserProjectsSet().add(this);
}
public void addTasksToProject(Task task){
this.projectTaskSet.add(task);
task.getTasksProjectSet().add(this);
}
//constructors, hashCode, equals, toString
}
Entity User
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String firstName;
#Column
private String lastName;
#ManyToMany(mappedBy = "projectUserSet", cascade = CascadeType.ALL)
private Set<Project> userProjectsSet = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "users_task",
joinColumns = {#JoinColumn(name = "users_id")},
inverseJoinColumns = {#JoinColumn(name = "task_id")})
private Set<Task> userTasksSet = new HashSet<>();
public void addTaskToUser(Task task) {
this.userTasksSet.add(task);
task.getTasksUserSet().add(this);
}
//constructors, hashCode, equals, toString
}
project and user initialization
Project project1 = new Project("Project1", "Project1");
User user1 = new User("User1", "User1");
project1.addUserToProject(user1);
With code shown below, table project_user is populated, verified using H2 console. In order to avoid stack overflow, I had to modify method Project#addTaskToUser as shown below.
Please note that only code relevant to the question, is included.
Normally, issue should be described by some tests. In this case, I added a CommandLineRunner that runs at startup.
CascadeType.ALL is not recommended for many-to-many relations, hence I changed this in code shown below.
Tested using H2 in-memory db.
Project class
#Data
#Entity
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "project_users",
joinColumns = #JoinColumn(name = "project_id"),
inverseJoinColumns = #JoinColumn(name = "users_id"))
private Set<MyUser> projectUserSet = new HashSet<>();
public void addUserToProject(User user) {
this.projectUserSet.add(user);
}
}
Project repo
public interface ProjectRepo extends JpaRepository<Project, Long> { }
User class
// cannot use #Data here because it will cause cyclic ref and stack overflow when accessing userProjectsSet
#Getter
#Setter
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#ManyToMany(mappedBy="projectUserSet")
private Set<Project> userProjectsSet = new HashSet<>();
}
User repo
public interface UserRepo extends JpaRepository<User, Long> {}
In app class
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Bean
CommandLineRunner run(ProjectRepo projectRepo, EntityManagerFactory entityManagerFactory) {
return args -> {
var testUser = new User();
testUser.setFirstName("first-name");
testUser.setLastName("last-name");
var project = new Project();
project.setName("project-name");
project.setDescription("project-description");
project.addUserToProject(testUser);
projectRepo.save(project);
// get saved user and print some properties
var userInDb = entityManagerFactory.createEntityManager().find(User.class, testUser.getId());
System.out.println(userInDb.getFirstName()); // prints "first-name"
System.out.println(userInDb.getUserProjectsSet().size()); // prints "1"
};
}
}
I am trying to write a multilanguage software. Using filters in hibernate, I want that every time an entity is accessed, only translation with the specific language being load.
#Entity
#Table(name = "province")
#FilterDef(name = "findByLanguage", parameters = {#ParamDef(name = "language", type = "string")})
public class Province {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(name = "country_id", nullable = false)
private Country country;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "province_id")
#Filter(name = "findByLanguage", condition = "language = :language")
private Set<ProvinceTranslation> translations;
}
This works as I expected for province, But Country has also a list of translations, and I want that CountryTranslation list is also contains only specified language when I load a province. But this code does not work:
#Entity
#Table(name = "country")
#FilterDef(name = "findByLanguage", parameters = {#ParamDef(name = "language", type = "string")})
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "country_id")
private List<Province> provinces;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "country_id")
#Filter(name = "findByLanguage", condition = "language = :language")
private Set<CountryTranslation> translations;
}
So if I load Country directly everything is ok, But if I load Province and access Country inside it, loading Country is different and #Filter is being ignored. How can I achieve this?
I have four class; UserGroup, UserAccount, Role, UserGroupRoleRelation and my db is IBM DB2
#Entity
#Table(name = "USER_GROUP")
public class UserGroup implements Serializable {
#Id
#Column(name = "USER_GROUP_ID")
#GeneratedValue
private Long id;
......
..
#OneToMany(mappedBy = "userGroup", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserGroupRoleRelation> userAccountsRole = new ArrayList<UserGroupRoleRelation>();
}
#Entity
#Table(name = "ROLE")
public class Role implements Serializable {
#Id
#Column(name = "ROLE_ID")
#GeneratedValue
private Long id;
......
#OneToMany(mappedBy = "role")
private List<UserGroupRoleRelation> userAccountInGroup = new ArrayList<UserGroupRoleRelation>();
}
#Entity
#Table(name = "USER_GROUP_ROLE_LINE", uniqueConstraints = #UniqueConstraint(columnNames = { "ROLE_ID", "USER_GROUP_ID" }))
public class UserGroupRoleRelation {
#Id
#GeneratedValue
#Column(name = "RELATION_ID")
private Long relationId;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "USER_ACCOUNT_USER_GROUP_ROLE_LINE", joinColumns = { #JoinColumn(name = "RELATION_ID") }, inverseJoinColumns = { #JoinColumn(name = "USER_ID") }, uniqueConstraints = #UniqueConstraint(columnNames = { "USER_ID", "RELATION_ID" }))
private List<UserAccount> userAccountList = new ArrayList<UserAccount>();
#ManyToOne
#JoinColumn(name = "USER_GROUP_ID")
private UserGroup userGroup;
#ManyToOne
#JoinColumn(name = "ROLE_ID")
private Role role;
}
#Entity
#Table(name = "USER_ACCOUNT")
public class UserAccount implements Serializable {
#Id
#Column(name = "USER_ID")
#GeneratedValue
private Long id;
.....
#ManyToMany(mappedBy = "userAccountList", cascade = CascadeType.ALL)
private List<UserGroupRoleRelation> rolesInGroup = new ArrayList<UserGroupRoleRelation>();
}
I wanna find usergroups of a useraccount and i prepared a method with criteria. its like;
#Override
#Transactional
public List<UserGroup> findUserGroupOf(UserAccount userAccount) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.createAlias("userAccountsRole", "userAccountsRole");
criteria.add(Restrictions.eq("userAccountsRole.userAccountList", userAccount));
return criteria.list();
}
But when i try to get result of that method, DB2 gives to me DB2 SQL Error: SQLCODE=-313, SQLSTATE=07004, SQLERRMC=null, DRIVER=3.63.75
Probably its about creating alias on many to many relation. I dont know what should i do to create alias on many to many. How can I get result of that function?
Thank
#Override
#Transactional
public List<UserGroup> findUserGroupOf(UserAccount userAccount) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.createAlias("userAccountsRole", "userAccountsRole");
criteria.createAlias("userAccountsRole.userAccountList", "userAccountList");
criteria.add(Restrictions.eq("userAccountList.id", userAccount.getId()));
return criteria.list();
}
It works for me. I mean criteria on "id". But I don't understand why I cant check equality on object instead of id when there is ManyToMany list
It is not of creating alias. You are passing an object to hibernate on which it can not make any criteria. You need to create bidirectional mapping for that.Or else if you your requirement is just to fetch the the list of UserAccountList of particular UserGroup class you can follow the below code.
#Override
#Transactional
public List<UserGroup> findUserGroupOf(long userGroupId) {
Criteria criteria = getSession().createCriteria(UserGroup.class);
criteria.add(Restrictions.eq("id",userGroupId));
criteria.createAlias("userAccountsRole", "uar");
criteria.setFetchMode("uar.userAccountList",FetchMode.JOIN);
return criteria.list();
}