spring-data-rest #ManyToMany - spring-data-jpa

I am working on a spring-data-rest module, but there is a problem in the query process:
I defined the roles attribute in the User class, added #ManyToMany annotation, and printed relevant sql about the Role during execution, but there was no relevant information about the Role in response. How should I query my role information as well?
Here is my code:
user class:
#Entity(name = "user")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(targetEntity = Role.class,cascade = CascadeType.MERGE,fetch = FetchType.EAGER)
#JoinTable(name = "user_role",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "role_id", referencedColumnName = "id")}
)
private Set<Role> roles = new HashSet<>();
}
role class:
#Entity(name = "role")
public class Role implements Serializable {
#Id
private Long id;
#ManyToMany(mappedBy = "roles",cascade = CascadeType.MERGE)
private Set<User> users = new HashSet<>();
UserRepository class:
#RepositoryRestController
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
#RequestMapping(value = "findByUsername",method = RequestMethod.GET)
User findByUsername(#RequestParam String username);
}
this is my response in postman:
enter image description here
so,why can't I get the role information in user?

Related

JPA Join with childEntity.childForeignEntity

I do not know how it is called in one word, but let me explain in details.
Lets assume I have following tables/schema in my database:
And following classes accordingly:
1.Post
#Entity
#Table(name = "posts")
public class Post {
#Id
private Long id;
#Column(name = "text")
private String text;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "post")
private Set<PostComment> postComments = new HashSet<>();
}
2.Post Comments
#Entity
#Table(name = "post_comments")
public class PostComment {
#Id
private Long id;
#Column(name = "post_id")
private Long postId;
#Column(name = "user_id")
private Long userId;
#Column(name = "text")
private String text;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="post_id")
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private User user;
}
3.User
#Entity
#Table(name = "users")
public class User {
#Id
private Long id;
#Column(name = "some_attributes")
private String someAttributes;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<PostComment> postComments = new HashSet<>();
}
How can I join Post with User via PostComment, so in my Post entity I could get all users commented:
#Entity
#Table(name = "posts")
public class Post {
....
//# join with post_comments.user_id
private Set<User> users = new HashSet<>();
....
}
Well, just get PostComment.user where PostComment.post equals your post.
#Query("select pc.user from PostComment pc where pc.post = :post")
List<User> getUsersWithComments(#Param("post") Post post);
Seems to work for me. Gives me the following SQL:
Hibernate: select user1_.id as id1_2_, user1_.some_attributes as some_att2_2_ from post_comments postcommen0_ inner join users user1_ on postcommen0_.user_id=user1_.id where postcommen0_.post_id=?
I don't know what this is all about:
#Column(name = "post_id")
private Long postId;
#Column(name = "user_id")
private Long userId;
or this
#JoinColumn(name="user_id")
#JoinColumn(name="post_id")
and you shouldn't do this:
= new HashSet<>();
and while we're at it this is redundant.
fetch = FetchType.LAZY,

spring data - how to filter users of a particular userProfile by userProfileType?

My User entity follows:
#Entity
#Table
public class User {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
// non relevant attributes
#ManyToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "user2userProfile",
joinColumns = #JoinColumn(name = "userId"),
inverseJoinColumns = #JoinColumn(name = "userProfileId"))
private Set<UserProfile> userProfileSet;
// getters and setters
}
The UserProfile entity follows:
#Entity
#Table(name = "userProfile")
public class UserProfile {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#Column(name = "type", length = 15, unique = true, nullable = false)
private String type = UserProfileType.USER.getUserProfileType();
// constructors, getter and setter
}
The UserProfileType enum is
public enum UserProfileType {
USER("USER"),
READER("READER"),
WRITER("WRITER"),
ADMIN("ADMIN");
// constructor and getter
}
My UserJpaRepository is:
public interface UserJpaRepository extends JpaRepository<User, String> {
// non relevant code
List<User> findAllByUserProfileType(UserProfileType userProfileType);
}
The way it stands now, I get the following error message on the console:
org.springframework.data.mapping.PropertyReferenceException: No property userProfileType found for type User!
What is the correct declaration on UserJpaRepository to get a list of users that have a specific UserProfileType (i.e. a list of all users that have a UserProfile of type READER)?
I don't really understand why you need to have a many to many relationship from your user to your user profile.
So if we would correct that to a many to one relationship like:
in User:
#OneToMany(mappedBy = "user")
private Set<UserProfile> profiles;
in UserProfile:
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
you could just setup a search by String type in your UserProfileRepository:
List<UserProfile> findAllByType(String type);
If you now iterate the List you get, you can get all users with a certain UserProfileType:
List<User> users = userProfileRepository.findAllByType(UserProfileType.USER.toString()).stream().map(profile -> profile.getUser()).collect(Collectors.toList());

How to fetch entities by objects value in JPA criteria api query

I am using JPA with JSF datatable with lazy loading.
Here One car can be owned by many users. So when i logged in to the application i want the cars which is owned by the user logged in(assume it as userId=1).
I have a mapping table "Cars_User" that contains carId and userId columns.
My Entities are like this
My Car Class
#Entity
#Table(name="car")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
#Transient
private boolean myCar;
#NotNull
#Size(min = 1, max = 50)
public String name;
#OneToMany(cascade = { CascadeType.REFRESH }, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinTable(name = "Cars_User", joinColumns = #JoinColumn(name = "carId"), inverseJoinColumns = #JoinColumn(name = "userId"))
private List<User> carUsers = new ArrayList<User>();
getters ...
setters ...
}
User Class
#Entity(name = "User")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstName;
private String lastName;
}
I have found one answer for Lists of String collection in this link but how can be achieved in my case.
I wanted to do get all Cars entities in criteria api that contains the logged in user id "userId" in carUsers Lists. can anyone please help?
I found the solution. I have passed the logged in user Object "user" in isMember function. This may help for somebody.
CriteriaBuilder criteriaBuilder = em.getEntityManagerFactory().getCriteriaBuilder();
CriteriaQuery<Car> criteria = criteriaBuilder.createQuery(Car.class);
Root<Car> root = criteria.from(Car.class);
criteria.where(criteriaBuilder.isMember(user, root.get(Car_.carUsers)));
List<Car> cars = em.createQuery(criteria).getResultList();

java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST

I have two 2 classes in relation many to many.
#Entity
#Table(name = "recipies")
public class Recipie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String url;
private String image;
#ManyToMany
#JoinTable(
name = "recipie_ingredients",
joinColumns = {
#JoinColumn(name = "recipie_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")})
private List<Ingredient> ingredients = new ArrayList<>();
#Entity
#Table(name = "ingredients")
public class Ingredient implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToMany(mappedBy = "ingredients")
private List<Recipie> recipies;
I would like to create a new recipie this way:
List<Ingredient> ingredientsList = new ArrayList<>();
String ingredientName = "example";
Ingredient ingredient = ingredientsDao.findIngredientByName(ingredientName);
if (ingredient == null) {
ingredient = new Ingredient();
ingredient.setName(ingredientName);
}
ingredientsList.add(ingredient);
.....
recipie.setIngredients(ingredientsList);
recipiesDao.addRecipie(recipie);
If ingredient doesn't exist in database, occur errors like this
Caused by: java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
Is there any way to Ingredient objects created in the table automatically?
I try add CascadeType.PERSIST but It also doesn't work
#ManyToMany(mappedBy = "ingredients", cascade = CascadeType.PERSIST)
private List<Recipie> recipies;
First of all, for a bidirectional relationship, both sides need to be updated, so:
recipe.getIngredients().add(ingredient);
ingredient.getRecipes().add(recipe);
Then, you can set the cascade to PERSIST on the side of the relationship which you are passing to save(). So if you are saving the recipe, you should mark the Recipe.ingredients with
#ManyToMany(cascade = CascadeType.PERSIST)
(Side note, it's spelled "recipe", not "recipie")
As mentioned by #Gimby, you need to assign both sides of the relationship.
When dealing with #Many... sided relationships I always initialise the collection (which you've done on one side):
#Entity
#Table(name = "recipies")
public class Recipie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String url;
private String image;
#ManyToMany
#JoinTable(
name = "recipie_ingredients",
joinColumns = {
#JoinColumn(name = "recipie_id", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "ingredient_id", referencedColumnName = "id")})
private List<Ingredient> ingredients = new ArrayList<>();
...
}
#Entity
#Table(name = "ingredients")
public class Ingredient implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToMany(mappedBy = "ingredients")
private List<Recipie> recipies = new ArrayList<>();
...
}
And then a slight variation in your logic:
String ingredientName = "example";
Ingredient ingredient = ingredientsDao.findIngredientByName(ingredientName);
if (ingredient == null) {
ingredient = new Ingredient();
ingredient.setName(ingredientName);
}
...
// Don't forget to assign both sides of the relationship
recipe.getIngredients().add(ingredient);
ingredient.getRecipies().add(recipe);
recipiesDao.addRecipe(recipe);
This should then cascade persist/update correctly.
The real fun will begin when you try to figure out how to associate a quantity with the ingredient...

Spring Data with Integration Test OneToMany ManyToOne

I know that this topic is not new, but I few days try to solve task.
I have 3 classes - Client, Account and Invoice.
Client has many Accoutns, Account has many Invoices.
I mapped them:
Client
#Entity
#Table(name = "CLIENT")
public final class Client implements Serializable {
...
#Column(length = 36, nullable = false, unique = true, updatable = false)
private String uuid;
#OneToMany(mappedBy = "client", cascade = CascadeType.ALL)
private List<Account> accounts;
...
}
Account
#Entity
#Table(name = "ACCOUNT")
public final class Account implements Serializable {
...
#ManyToOne
#JoinColumn(name = "client_uuid", referencedColumnName = "uuid", nullable = false)
private Client client;
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL)
private List<Invoice> invoices;
...
}
Invoice
#Entity
#Table(name = "INVOICE")
public final class Invoice implements Serializable {
#ManyToOne
#JoinColumn(name = "account_uuid", referencedColumnName = "uuid", nullable = false)
private Account account;
}
I use Spring Data Jpa:
#Repository
public interface SpringDataClientRepository extends ClientRepository, JpaRepository<Client, Integer> {
Others the same.
When I try to run ITests, test with client work fine:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { JpaConfig.class, ITDaoConfig.class })
public class ITClientRepository {
private static final Logger log = Logger.getLogger(ITClientRepository.class);
#Autowired
private ClientRepository clientRepository;
#Test
#Transactional
public void testSaveClient() {
log.info("testSaveClient start");
Client client = new Client();
client.setName("testSaveClient");
client.setUuid("client-testSaveClient");
client.setTelephone("12345679");
Account account = new Account();
account.setClient(client);
account.setMoneyCount(10);
account.setUuid("client-testSaveClient");
client.addAccount(account);
log.info(client.toString());
Client getClient = clientRepository.save(client);
log.info(client.toString());
It saves client and account in. But this test:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { JpaConfig.class, ITDaoConfig.class })
public class ITAccountRepository {
private static final Logger log = Logger.getLogger(ITAccountRepository.class);
#Autowired
private AccountRepository accountRepository;
#Test
#Transactional
public void testSaveAccount() {
log.info("testSaveAccount start");
Client client = new Client();
client.setName("testSaveAccount");
client.setTelephone("12345679");
client.setUuid("account-testSaveAccount");
client.setId(200);
// Client saved in db
Account account = new Account();
account.setClient(client);
account.setMoneyCount(15);
account.setUuid("account-testSaveAccount");
client.addAccount(account);
Invoice invoice = new Invoice();
invoice.setAccount(account);
invoice.setAmount(11);
Date date = new Date();
invoice.setCreated(date);
invoice.setUuid("account-testSaveClient");
invoice.setDescription("Description of invoice");
account.addInvoice(invoice);
log.info(account.toString());
Account getAccount = accountRepository.save(account);
log.info(account.toString());
fail with:
Caused by: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : projects.model.Invoice.account -> projects.model.Account
I want that all Invoices will be saved if I save Account of those invoices. And the same with Client - all Accounts will be saved if I save Client of them. How I do this?
I made a change to have a unidirectional relationship with composition:
Client
#Entity
#Table(name = "CLIENT")
public final class Client extends BaseEntity {
#Column(length = 36)
private String uuid;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "client_id", referencedColumnName = "id")
private List<Account> accounts;
}
Invoice
#Entity
#Table(name = "INVOICE")
public final class Invoice extends BaseEntity {
#Column(length = 36)
private String uuid;
}
Account
#Entity
#Table(name = "ACCOUNT")
public final class Account extends BaseEntity {
#Column(length = 36)
private String uuid;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "account_id", referencedColumnName = "id")
private List<Invoice> invoices;
}
In the test, I left everything like it was. And it all works now.