Mapping nested collections with hibernate JPA annotations? Map<Key,Set<Values>> - spring

I'm not sure how to annotate the bellow classes correctly. What I'm trying to do is to have on the User entity, a list of roles for a mapped Asset.
#Entity
class Asset{
#Id #GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = -1
}
#Entity
class Role {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = -1
}
#Entity
class User{
#Id #GeneratedValue(strategy = GenerationType.AUTO)
var id: Long = -1
???
var roles: Map<Asset, Set<Role>> = HashMap()
}
From what I discovered hibernate does not support a map of sets (link here). So I tried other options as an intermediate class like this.
But I'm struggling with the annotations. Can someone please tell what annotations I have to put on the three classes?
If someone knows a better approach instead of the map, to have the user roles by asset, please tell.

Related

Creating subcategories in kotlin spring boot

I need to implement categories and subcategories within my entities. Here's what I have so far and think it should be:
StockCategory.kt
#Entity
#Table(name = "table_categories")
data class StockCategory(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id")
val id: Long? = null,
#ManyToOne
#JoinColumn(name = "parentid")
val parent: StockCategory? = null,
#ManyToMany(mappedBy = "categories")
var stockItems: MutableList<StockItem> = mutableListOf(),
#OneToMany(mappedBy = "parent")
var childCategories: MutableList<StockCategory> = mutableListOf(),
)
StockItem.kt
#Entity
#Table(name = "table_stock")
data class StockItem(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "stock_item_id")
val id: Long? = null,
#Column(name = "stock_item_name")
var name: String = "New Item",
...
#ManyToMany
#JoinColumn(name = "item_category", referencedColumnName = "category_id")
var categories: MutableList<StockCategory> = mutableListOf(),
...
)
Now at the moment, this looks to be correct... At the very least Spring Boot is not complaining.
However, in terms of what to do next, I'm not sure. I know I need to implement a JpaRepository, of which I have the current:
StockCategoryRepository.kt
interface StockCategoryRepository: JpaRepository<StockCategory, Long> {
}
I also need to implement the relevant methods in my service class.
What exactly do I need to do next in order to get this to work and be able to use the information later on? Please also ELI5 too as although I have a decent amount of knowledge on this, I'm still not where I would like to be when it comes to this.
A few background bits if it makes it easier for you.
I'm using H2 as my database, Spring Boot and Kotlin as my language.

Why should we use #Enumerated in JPA?

I have an enum named difficulty and I marked it with #Entity and it has a one to one relationship with the recipe class. I ran my program in spring boot and it ran without any problem. My question is, why do we have to use #Enumerated? What's wrong with the way I went?
Enum:
#Entity
public enum Difficulty {
EASY,MODERATE,HARD;
#OneToOne
private Recipe recipe;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public Long getId() {
return id;
}
}
Recipe class:
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne
private Difficulty difficulty;
}
with #Enumareted:
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne
#Enumerated(value=EnumType.STRING)
private Difficulty difficulty;
}
If we go with #Enumrated, we have to remove #Entity from Difficulty(I wrote Recipe which corrected), which I didn't write.
Enum are not associations (although you can create a colletion of enums with #ElementCollection, but it's a different scenario), so you don't need the #OneToOne if you use #Enumerated. The mapping should be:
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Enumerated(STRING)
private Difficulty difficulty;
}
enum Difficulty {EASY,MODERATE, HARD;}
The value will be stored as a column in the table representing Recipe.
A one-to-one association means that every time you save a recipe, you are also creating a new Difficulty. You are using enums, so I don't think that's what you want (are you sure your mapping actually works?).
Without using enums, a mapping that would make sense, would be:
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
private Difficulty difficulty;
}
#Entity
public class Difficulty {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long difficultyId;
private String level;
}
or, if you want to use enum and associations:
#Entity
public class Difficulty {
enum Level {EASY, MEDIUM, HARD;}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long difficultyId;
#Enumerated(STRING)
private Level level;
}
Having a different entity means that the difficulties are stored as rows in a separate table Difficulty. Because it's a many-to-one association, you can associate different recipes to the same difficulty.
It also means that Hibernate will need a join or a separate query to load the difficulty. I don't see the benefit if you are only interested in the name of the difficulty.
Ultimately, it depends on your use case and how you want the data to appear in the database.
But if you can use enum and there is only one level per recipe, mapping it as a column seems a simpler solution.

How to make IDs non-sequencial?

I have an entity
#Entity
data class Person (
#Id #GeneratedValue
val id: Long
)
But I noticed the values for id are consecutive. Is there a way to let Spring Boot make them more random?
You can create a custom identifier generator and use it.
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq")
#GenericGenerator(
name = "seq",
strategy = "com.java.generators.SequenceIdGenerator",
parameters = {...})
private String id;
...
}
Here you should create com.java.generators.SequenceIdGenerator by own
A good tutorial about this

findBy not working with inherited properties

I have the following model and repository:
#Entity
#Table(name = "db_user", uniqueConstraints = { #UniqueConstraint(columnNames = "email") })
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_user")
#SequenceGenerator(name = "seq_user", sequenceName = "seq_user")
#Column(name = "id")
private Long id;
// ...
}
#Entity
#Table(name = "movie")
public class Movie extends AbstractItem {
// Id column inherited from AbstractItem
// ...
}
#Entity
#Table(name = "movie_user")
public class MovieOwnership extends AbstractOwnership {
#ManyToOne
private Movie movie;
// ...
}
#MappedSuperclass
public abstract class AbstractOwnership{
#Id
#SequenceGenerator(name = "seq_default", sequenceName = "seq_default")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_default")
#Column(name = "id")
private Long id;
#ManyToOne
private User owner;
// ...
}
public interface MovieOwnershipRepository extends QueryDslJpaRepository<MovieOwnership, Long> {
List<MovieOwnership> findByOwnerId(Long ownerId);
MovieOwnership findByOwnerIdAndMovie(Long ownerId, Movie movieId);
List<MovieOwnership> findByOwnerIdAndMovieIdIn(Long ownerId, Set<Long> movieIds);
}
I'm trying to use Spring's findBy requests to fetch MovieOwnerships by owner or movie, using the id field of both entities. I'm able to work directly with the owner's id, but using MovieId in my requests seems broken (I can use the whole Movie object though). In the code above, the first two findBy are fine but the last one throws this exception:
Caused by: java.lang.IllegalArgumentException: Unable to locate
Attribute with the the given name [movieId] on this ManagedType
[carrm.app.data.AbstractOwnership]
It compiles if I try with another property from Movie (like findByMovieTitle), but I can't make it work on the id.
Any idea how to solve this?
I tried the same with JpaRepository instead of QueryDslJpaRepository.
The SQL is generated correctly:
select movieowner0_.id as id1_1_, movieowner0_.owner_id as owner_id2_1_, movieowner0_.movie_id as movie_id3_1_
from movie_ownership movieowner0_
left outer join user user1_ on movieowner0_.owner_id=user1_.id
left outer join movie movie2_ on movieowner0_.movie_id=movie2_.id
where user1_.id=? and (movie2_.id in (?))
So it must be a QueryDslJpaRepository implementation bug.
I would suggest you use JpaRepository instead.

Spring JPA not in

I have two class Drug and medicalDrawer.
I search a way to get all medicalDrawer not used in Drug
#Entity
public class Drug {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long drugId;
#OneToOne
private MedicalDrawer medicalDrawer;
}
#Entity
public class MedicalDrawer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long medicalDrawerId;
private String name;
}
If you are using Spring and JPARepository, it supports quite a lot keywords like : 'And', 'IsNull', similarly 'NotIn'.
Check this out: http://docs.spring.io/spring-data/jpa/docs/current/reference/html/
Now, coming to your question, here is my approach:
Get all the drugs using findAll(), loop through and add medicalDrawerId in a list say medicalDrawerDrugList.
Find MedicalDrawer, which are not in drugs. For that, you can
write something like this:
findByMedicalDrawerIdNotIn(medicalDrawerDrugList);
I hope, this helped.

Resources