I'm using Spring Boot and MySQL. I tried to add new entity(songs) in playlist table. They have many to many relationship. But as you can see in answer after mysql query it doesn't saved.
Other relationships work correctly
PlaylistEntity
#Data
#Entity
#Component
#Getter
#EqualsAndHashCode(exclude = "songs")
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "playlists")
public class PlaylistEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
private String playlistTitle;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
#JoinColumn(name = "user_id", nullable = false)
private UserEntity user;
private LocalDateTime createdAt;
public PlaylistEntity(String playlistTitle, UserEntity user, LocalDateTime createdAt) {
this.playlistTitle = playlistTitle;
this.user = user;
this.createdAt = createdAt;
}
#Transient
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "playlist_song",
joinColumns = #JoinColumn(name = "playlist_id", nullable=false),
inverseJoinColumns = #JoinColumn(name = "song_id", nullable=false))
private Set<SongEntity> songs = new HashSet<>();
}
PlaylistRepository
#Repository
public interface PlaylistRepository extends PagingAndSortingRepository<PlaylistEntity, String> {
#Query(value = "select * from playlists where user_id = :id", nativeQuery = true)
List<PlaylistEntity> showAllUserPlaylists(#Param("id") String id);
#Query(value = "select * from playlists where playlist_title = :playlist_title", nativeQuery = true)
PlaylistEntity findByName(#Param("playlist_title") String playlist_title);
}
SongEntity
#Data
#Entity
#Builder
#Getter
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "songs")
public class SongEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
private String title;
private String artist;
private String album;
private String genre;
#CreationTimestamp
private LocalDateTime releaseDate;
private int likes;
#Transient
#EqualsAndHashCode.Exclude
#ManyToMany(mappedBy = "songs")
private Set<PlaylistEntity> playlistEntities = new HashSet<>();
#Transient
#ManyToMany(mappedBy = "songs")
#EqualsAndHashCode.Exclude
private Set<SubscriptionEntity> subscriptionEntities = new HashSet<>();
public SongEntity(String name) {
this.title = name;
}
}
SongRepository
#Repository
public interface SongRepository extends JpaRepository<SongEntity, String> {
#Query(value="SELECT * FROM songs WHERE (:genre is null or genre = :genre) " +
"AND (:artist IS NULL or artist = :artist)", nativeQuery=true)
List<SongEntity> findByParams(#Param("genre") String genre, #Param("artist") String artist);
#Query(value="SELECT * FROM songs WHERE artist = :artist", nativeQuery=true)
List<SongEntity> findByArtist(#Param("artist") String artist);
#Query(value="SELECT * FROM songs WHERE genre = :genre", nativeQuery=true)
List<SongEntity> findByGenre(#Param("genre") String genre);
#Query(value = "SELECT s.title, s.likes FROM SongEntity s WHERE s.artist = :artist")
List<SongEntity> showSongsStatistics(#Param("artist") String artist);
}
Method where I saved song in playlist table
#Transactional
public Playlist addSongToPlaylist(String playlistId, String songId) throws Exception {
SongEntity addedSong = findSongById(songId)
PlaylistEntity requiredPlaylist = findPlaylistById(playlistId);
requiredPlaylist.getSongs().add(addedSong);
PlaylistEntity updatedPlaylist = playlistRepository.save(requiredPlaylist);
return playlistConverter.fromEntity(updatedPlaylist);
}
And controller
#Slf4j
#Configuration
#RestController
#AllArgsConstructor
#RequestMapping("/user/playlists")
public class PlaylistController {
private final PlaylistService playlistService;
#PostMapping(value = ADD_SONG_TO_PLAYLIST_URL)
Playlist addSongToThePlaylist(#RequestParam String playlistId, #RequestParam String songId) throws Exception {
return playlistService.addSongToPlaylist(playlistId, songId);
}
#UtilityClass
public static class Links {
public static final String ADD_SONG_TO_PLAYLIST_URL = "/addSong";
}
}
I use Postman to make requests. And after request I take this answer, which shows that song was added to playlist.
https://i.stack.imgur.com/VdfbC.png
But as I said, if check playlist_song db, its nothing there. it means that my program doest correctly save many-to-many tables.
https://i.stack.imgur.com/9lgW9.png
And in logs there are any exceptions.
So I can understand what is wrong.
Hope somebody has an idea.
For SongEntity's #EqualsAndHashCode(onlyExplicitlyIncluded = true), you don't have any explicitly included annotations. You have a few listed to Exclude, but based on the docs at https://projectlombok.org/features/EqualsAndHashCode, it seems to require you explicitly mark them as Included when you use this flag.
I'm not 100% sure what Lombok does when you put this flag at the class level but then don't explicitly include anything, but it seems like an invalid state and may be messing up your Equals and Hashcode and consequently messing up ability to track the items in the HashSet bucket that represents the songs that you're adding to.
So I'd start by fixing this so your Equals/HashCode is correct. Which looks like you'd want to either remove the onlyExplicitlyIncluded=true settings altogether OR you actually add specific Includes.
Here is a thread that talks about using explicit Includes if you want to stick with that route:
How to use #EqualsAndHashCode With Include - Lombok
Also, why do you have #Transient annotations on your relationships? That annotation usually tells the EntityManager to ignore the content it's attached to. In your case, if you're adding something to the Set but marking the Set as #Transient, then intuitively it should not affect in the Database. Suggest you remove those annotations where you want changes to objects in the relationships/sets to actually be reflected in the database.
Related
I'm learning how Spring framework works and as an example I'm trying to save cities and countries which users can log using the API endpoints. However, I can't figure out how to prevent duplicate entries.
For example I'm adding 2 cities in a country using the endpoint (photo below) but in the Country table I get duplicate values. How can I prevent duplicate values ? Thanks in advance.
#Getter
#Setter
#Entity
#Table(name = "COUNTRY")
public class CntCountry {
#Id
#SequenceGenerator(name = "CntCountry", sequenceName = "CNT_COUNTRY_ID_SEQ")
#GeneratedValue(generator = "CntCountry")
private Long id;
#Column(name = "COUNTRY_NAME", length = 30, nullable = false)
private String countryName;
#Column(name = "COUNTRY_CODE", length = 30, nullable = false)
private String countryCode;
}
#Getter
#Setter
#Table(name = "CITY")
#Entity
public class CtyCity {
#Id
#SequenceGenerator(name = "CtyCity", sequenceName = "CTY_CITY_ID_SEQ")
#GeneratedValue(generator = "CtyCity")
private Long id;
#Column(name = "CITY_NAME", length = 30, nullable = false)
private String cityName;
#Column(name = "PLATE_NUMBER", length = 30, nullable = false)
private Long plateNumber;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "FK_COUNTRY")
private CntCountry country;
}
EDIT:
#PostMapping("/city")
public ResponseEntity<CtyCityDto> save(#RequestBody CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCityDto ctyCityDto = ctyCityService.save(ctyCitySaveRequestDto);
return ResponseEntity.ok(ctyCityDto);
}
#Service
#AllArgsConstructor
public class CtyCityService {
private CtyCityDao ctyCityDao;
public CtyCityDto save(CtyCitySaveRequestDto ctyCitySaveRequestDto){
CtyCity ctyCity = CtyCityMapper.INSTANCE.convertToCtyCity(ctyCitySaveRequestDto);
ctyCity = ctyCityDao.save(ctyCity);
CtyCityDto ctyCityDto = CtyCityMapper.INSTANCE.convertToCtyCityDto(ctyCity);
return ctyCityDto;
}
}
public interface CtyCityDao extends JpaRepository<CtyCity,Long> {
}
#Data
public class CtyCityDto {
private Long id;
private String cityName;
private Long plateNumber;
private CntCountry country;
}
I'm not really following your naming conventions, and I think your DTO classes are just complicating things for you at this point... But in general terms, because the entities you're sending have no id value associated with them, JPA assumes they are different objects and adds them to the database with new id's because it hasn't been told anywhere that similar items might in fact be the same object, it needs to be told.
I can think of 2 ways to prevent entity duplication in your database.
1. The easiest way would be to set your Country and City names (or other attributes) to be "unique", you can do this in your entity classes simply by adding unique = true to the column data on the item you wish to be unique.
//In Country.java
#Column(name = "COUNTRY_NAME", length = 30, nullable = false, unique = true)
private String countryName;
//In City.java
#Column(name = "CITY_NAME", length = 30, nullable = false, unique = true)
private String cityName;
Although, you will then need to handle exceptions thrown if a duplicate is provided, in Spring Boot the best way to handle this is with a #ControllerAdvice but that's another subject.
2. Check if the entity exists by name or some other value. A common approach might be something like the following:
//In your service
public Object saveCountry(Country country){
Country existingCountry = countryRepository.findByName(country.getName()).orElse(null);
if(existingCountry == null){
//Country does not already exist so save the new Country
return countryRepository.save(country);
}
//The Country was found by name, so don't add a duplicate
else return "A Country with that name already exists";
}
//In your Country repository
Optional<Country> findByName(countryName);
In case my answer doesn't make sense, I have thrown together an example following my first suggestion (using the unique column attribute and a controller advice) which you can view/clone from here
I would like to use the EntityGraph Feature because of the known n+1 Problem. I have the following Entities structure:
#Entity
#Table(name = "customer")
public class Customer extends Person {
#Column(name = "foo")
public String foo;
#Column(name = "bar")
public String bar;
}
#Entity
#Table(name = "person")
public class Person {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "car.id")
public Car car;
#Embedded
public Key key;
}
#Entity
#Table(name = "car")
public class Car {
#Column(name = "a")
public String a;
#Column(name = "b")
public String b;
}
#Embeddable
public class Key
{
#Column(name = "key_id")
public Long keyId;
#Column(name = "key_color")
public String keyColor;
}
Now I want to use a NamedEntityGraph. As far as I understand with "#NamedEntityGraph(name = "getCustomer", includeAllAttributes=true)" it should work but it doesnt.
The NamedEntityGraph call with
em.createQuery(criteriaQuery).setHint("javax.persistence.fetchgraph", em.getEntityGraph("getCustomer")).getResultList()
returns the amount of Customers in the database but all Attributes including car and the Embedded Attribute key is always null.
Do I have to use subgraphs? I tried to declare the NamedEntityGraph on Customer class also on Person class. It makes no difference.
EDIT:
After struggling a long time with this problem, i tried to break down it to the lowest level with these two entities
#Entity
#Table(name = "publication")
#NamedEntityGraph(name = "graph.Publication.articles",
attributeNodes = #NamedAttributeNode("articles"))
public class Publication {
#Id
private String publicationId;
private String name;
private String category;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "publicationId")
private List<Article> articles;
#Entity
#Table(name = "article")
public class Article {
#Id
private String articleId;
private String title;
private String publicationId;
}
If i create a query i can see further more than one query in the postgres log.
EntityGraph<?> entityGraph = em.getEntityGraph("graph.Publication.articles");
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("javax.persistence.fetchgraph", entityGraph).getResultList();
Different queries for all publications
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_1'
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_2'
But I would only have expected one query with a join here.
Finally I found a solution for my problem. I refer to the edited part of my question.
I found this page which describes very well how to use batch query hints to improve performance.
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html?m=1
For my example I don't need the entitygraph anymore. The query should created like this
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("eclipselink.batch", "x.articles").getResultList();
Problem I'm trying to solve
I'm trying to model a #ManyToMany relation between a User and Role, such that a user can have n roles, and one role is referenced by several users. A role can be persisted even if it's not referenced by any user (detached), and a user with no roles is allowed too.
The same kind of relation must be built between Role and ResourcePermission.
To give you an idea about how each entity looks like:
Both ResourcePermission and Role have a finite set of values. For example, if Patient happens to be a resource, then one resource permission could be "PATIENT:READ" or "PATIENT:WRITE", and the role DOCTOR has several of these permissions. I hope it's clear sofar how my data model looks like.
What I'm using
Currently, I'm using spring-data-jpa version 2.4.2 to model my entities, and to create my CRUD repos. Except for base path and media type, I don't have any specific configuration (all is set to default).
Hibernate is my persistence provider atm .
Concerning my datasource, I'm using in-memory H2 for my development environment, and again no specific configuration there either.
How I'm solving it
Here's how my entities look like
User.java
#Table
#Entity
#Data
public class User implements Serializable {
private static final long serialVersionUID = 1123146940559321847L;
#Id
#GeneratedValue(generator = "user-id-generator")
#GenericGenerator(name = "user-id-generator",
strategy = "....security.entity.UserIdGenerator",
parameters = #Parameter(name = "prefix", value = "USER-")
)
#Column(unique = true, nullable = false)
private String id;
#Column
private int age;
#Column(unique = true, nullable = false)
private String username;
#Column(unique = false, nullable = false)
private String password;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.MERGE
)
#JoinTable(
name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private List<Role> roles = Collections.emptyList();
public User withId(final String id) {
this.id = id;
return this;
}
public User withAge(final int age) {
this.age = age;
return this;
}
public User withUsername(final String username) {
this.username = username;
return this;
}
public User withPassword(final String password) {
this.password = password;
return this;
}
public User withRoles(final Role... roles) {
return withRoles(Arrays.stream(roles).collect(Collectors.toList()));
}
public User withRoles(final List<Role> roles) {
this.roles = roles;
return this;
}
}
Role.java
#Data
#NoArgsConstructor
#Table
#Entity
public class Role implements Serializable {
private static final long serialVersionUID = 812344454009121807L;
#Id
private String roleName;
#ManyToMany(
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
#JoinTable(
name = "role_resource_permission",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "resource_permission_id")
)
private Set<ResourcePermission> resourcePermissions = Collections.emptySet();
#ManyToMany(
mappedBy = "roles",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private List<User> users = Collections.emptyList();
public Role(final String roleName) {
setRoleName(roleName);
}
public void setRoleName(final String roleName) {
final RoleType roleType = RoleType.of(roleName);
this.roleName = roleType.getRoleName();
final Set<ResourcePermission> resourcePermissions = roleType.getResourcePermissions().stream()
.map(ResourcePermissionType::getPermissionName)
.map(ResourcePermission::new)
.collect(Collectors.toSet());
setResourcePermissions(resourcePermissions);
}
public void setResourcePermissions(final Set<ResourcePermission> resourcePermissions) {
if (this.resourcePermissions.isEmpty()) {
this.resourcePermissions = resourcePermissions;
}
}
}
ResourcePermission.java
#NoArgsConstructor
#Data
#Table
#Entity
public class ResourcePermission implements Serializable {
private static final long serialVersionUID = 883231454000721867L;
#Id
private String permissionName;
public ResourcePermission(final String permissionName) {
setPermissionName(permissionName);
}
#ManyToMany(
mappedBy = "resourcePermissions",
fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.DETACH }
)
private Set<Role> roles = Collections.emptySet();
public void setPermissionName(String permissionName) {
final ResourcePermissionType permissionType = ResourcePermissionType.of(permissionName);
this.permissionName = permissionType.getPermissionName();
}
}
RoleType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum RoleType {
DOCTOR("DOCTOR", doctorsPermissions()),
TECHNICIAN("TECHNICIAN", technicianPermission()),
ADMIN("ADMIN", adminPermissions());
#Getter
private String roleName;
#Getter
private final List<ResourcePermissionType> resourcePermissions;
public static RoleType of(final String roleName) {
return Arrays.stream(values())
.filter(roleType -> roleType.getRoleName().equals(roleName.toUpperCase()))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
private static List<ResourcePermissionType> doctorsPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
PATIENT_READ, PATIENT_WRITE
);
}
private static List<ResourcePermissionType> adminPermissions() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE,
ORG_UNIT_READ, ORG_UNIT_WRITE
);
}
private static List<ResourcePermissionType> technicianPermission() {
return Arrays.asList(
ENCOUNTER_READ, ENCOUNTER_WRITE,
BUILDING_UNIT_READ, BUILDING_UNIT_WRITE
);
}
}
ResourcePermissoinType.java
#AllArgsConstructor(access = AccessLevel.PRIVATE)
public enum ResourcePermissionType implements Serializable {
PATIENT_READ("PATIENT:READ"), PATIENT_WRITE("PATIENT:WRITE"),
ENCOUNTER_READ("ENCOUNTER:READ"), ENCOUNTER_WRITE("ENCOUNTER:WRITE"),
BUILDING_UNIT_READ("BUILDING_UNIT:READ"), BUILDING_UNIT_WRITE("BUILDING_UNIT:WRITE"),
ORG_UNIT_READ("ORG_UNIT:READ"), ORG_UNIT_WRITE("ORG_UNIT:WRITE");
#Getter
private String permissionName;
public static ResourcePermissionType of(final String permissionName) {
return Arrays.stream(values())
.filter(v -> v.getPermissionName().equals((permissionName.toUpperCase())))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
Unfortunately, the javax persistence API does not accept enums as entities. I tried using #Embeddable and #IdClass too, but that didn't work out for me either. I was not able to generate the schema that I had in mind. On the other hand, the schema was successfully generated using this model.
At the moment, both the Role repository as well as the Resource Permission repository are not exported (#RepositoryRestResource(..., exported = false)), so in order for you to persist those two entities, you'd have to provide that data in User. Keep that in mind, because that's also a part of the discussion that I want to talk about.
Now let's examine this integration test for the UserCrudRepository that will attempt to add a new user after a successful authentication.
#TestMethodOrder(OrderAnnotation.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
class UserCrudRepositoryApiITest {
private final List<User> testUsers = Arrays.asList(
new User().withUsername("dummy_username_01").withPassword("dummy_password_01").withAge(35)
.withRoles(new Role("ADMIN")),
new User().withUsername("dummy_username_02").withPassword("dummy_password_02").withAge(40)
.withRoles(new Role("DOCTOR")),
new User().withUsername("dummy_username_03").withPassword("dummy_password_03").withAge(45)
);
.
.
#Order(1)
#Test
public void afterAuthenticationAddNewUser() throws Exception {
final String generatedToken = login();
// serialize the user
final String requestJson = objectMapper.writeValueAsString(testUsers.get(0));
final RequestBuilder request = MockMvcRequestBuilders.post(USER_CRUD_BASE_URL)
.header(HttpHeaders.AUTHORIZATION, generatedToken)
.contentType(MediaType.APPLICATION_JSON)
.content(requestJson);
final String serializedContent = mvc.perform(request)
.andExpect(status().isCreated())
.andReturn()
.getResponse()
.getContentAsString();
final User storedUser = objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(serializedContent, User.class);
assertThat(storedUser).isEqualTo(testUsers.get(0));
}
.
.
}
In here, I'm getting a status code conflict 409, and not able to persist all entities at once.
Unfortunately, SO allows only 30000 character, so please navigate to this repo if you would like to take a look at the log.
My Questions
I couldn't for the life of me understand where that referential integrity constraint violation
is occurring. Any idea?
Any suggestions on how to model these relations in a better way are welcome!
Another problem I'm having with JPA repos is that the only way to persist roles and resource permissions is by providing that data in the user's body. I would like those entities to be managed independently of the user (each with its own separate repository), so I tried exporting their repositories. However, the problem then is that you no longer can pass Role data in the body of a User, but rather A reference to that entity. Is there a way to get the best of both worlds.
I hope I made my problem clear, if not, I'd be happy to elaborate more.
I guess when a User is persisted, it also does the insert for the user_role table, but the role wasn't persisted yet. You could try to persist the Role first or use PERSIST cascading at the User#roles association.
I'm using Spring Boot 2.3.0 along with Spring Data JPA and Spring MVC. I have the following 2 entities:
Country.java
#Entity
#Table(name = "countries")
public class Country
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "country_id")
private Integer id;
#Column(name = "country_name" , nullable = false , length = 50)
#NotNull #Size(max = 50)
private String name;
#Column(name = "country_acronym" , length = 3)
#Size(max = 3)
private String acronym;
//Getters-Setters
//Equals-Hashcode (determines equality based only on name attribute)
}
City.java
#Entity
#Table(name = "cities")
public class City
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "city_name")
#Size(max = 50)
private String name;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "city_country")
private Country country;
//Getters-Setters
//Equals/Hashcode (all attributes)
}
What I want to achieve is to save cities through REST calls. One catch is that I want in the body of the request to provide only the name of the country and if that country exists in the countries table, then it must be able to find the reference by itself , else it should first insert a new country and then match the reference.
For complete reference, let me provide the Repository and Controller classes:
CityRepository.java
#Repository
public interface CityRepository extends JpaRepository<City,Integer>
{
}
MainController.java
#RestController
public class MainController
{
#Autowired
private CityRepository cityRepository;
#PostMapping(value = "/countries")
private void insertCountry(#RequestBody #Valid Country country)
{
countryRepository.save(country);
}
#PostMapping(value = "/cities")
public void insertCities(#RequestBody #Valid City city)
{
cityRepository.save(city);
}
}
A sample body of a request:
{
"name": "Nikaia",
"country": {
"name": "Greece"
}
}
The error I get is that Hibernate always tries to save the country, it never looks if it exists (I get a constraint violation). I guess that the country never gets proxied by Hibernate because it isn't yet a persisted entity. Is there a way I can easily solve that using Data JPA ? Or should I go a level lower and play with the EntityManager? Complete code samples would be greatly appreciated.
It's logical. For your desire feature fetch the country by country name. If exist then set that in city object.
#PostMapping(value = "/cities")
public void insertCities(#RequestBody #Valid City city)
{
Country country = countryRepository.findByName(city.getCountry().getName());
if(country != null) city.setCountry(country);
cityRepository.save(city);
}
And findByName in CountryRepository also
Country findByName(String name);
I have a Many to Many relationship between two entities, User and Movies in a spring boot application with spring security.
I want to made a REST API that finds all the movies and includes a new field that shows if the logged user did watched the movie or no.
I can't find an easy way to do it, I only found a solution creating a new DTO object in the query. I show my code next.
The entities are the following:
Movies:
#Entity
#Table(name = "movies")
public class Movies implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Size(max = 100)
#Column(name = "name", length = 100, nullable = false)
private String name;
#Column(name = "jhi_year")
private Long year;
#Size(max = 100)
#Column(name = "category", length = 100)
private String category;
#ManyToMany(mappedBy = "movies")
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<User> users = new HashSet<>();
User:
#Entity
#Table(name = "user")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Size(max = 50)
#Column(name = "login", length = 50)
private String login;
#Size(max = 250)
#Column(name = "bio", length = 250)
private String bio;
#ManyToMany
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JoinTable(name = "user_movies",
joinColumns = #JoinColumn(name="user_id", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="movies_id", referencedColumnName="id"))
What I did is a #Repository class with a JPQL #query like this one:
#Query("select new com.test.service.dto.MoviesDTO(movies.id, movies.name, movies.year, movies.category, " +
" count(users.id)) from Movies movies left join movies.users users on users.login = ?#{principal.username} group by movies.id ")
Page<MoviesDTO> findAll(Pageable pageable);
This works fine, but is there a simpler way to do the this? It would be perfect to find a method to do this adding a new object in the entity class, avoiding making a new DTO object in the query.
I know that it is almost same issue than this one posted by myself some time ago, which remains a mystery to me.
Many, many thanks!
Thanks to jasarez for the answer. I changed the DOT for a projection and it works like a charm!
The repository:
#Query("select movies.id as id, movies.name as name, movies.year as year, movies.category as category, count(users.id) as moviesCount " +
"from Movies movies left join movies.users users on users.login = ?#{principal.username} group by movies.id")
Page<MovieWithUserData> findAllWithUserData(Pageable pageable);
and the projection:
public interface MovieWithUserData {
Long getId();
String getName();
String getCategory();
Integer getYear();
Long getMoviesCount();
}