Spring Data with Integration Test OneToMany ManyToOne - jpa

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.

Related

spring-data-rest #ManyToMany

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?

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());

#OneToMany Pesist Child when creating Parent Entity

I have a OneToMany Relationship (User to EmailAddress)
Maybe I'm going about this the wrong way, My Database is empty but If I want to POST a User object and add it to the Database, along with the emailAdresses object and have the EmailAddress persisted also.
I want 2 records in the Database:
1 User and 1 EmailAddress (with a fk to User table)
Service Class
Currently what I've implemented to get this to work is this:
#Service
public class UserService {
private UserRepository userRepository;
private ModelMapper modelMapper;
public UserService(UserRepository userRepository, ModelMapper modelMapper) {
this.userRepository = userRepository;
this.modelMapper = modelMapper;
//Used for mapping List
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(Configuration.AccessLevel.PRIVATE)
.setSourceNamingConvention(NamingConventions.JAVABEANS_MUTATOR);
}
public User createUser(UserCreateDTO userCreateDTO) {
User user = modelMapper.map(userCreateDTO, User.class);
//persist User to EmailAddress object
if(user.getEmailAddresses() != null){
user.getEmailAddresses().forEach(user::persistUser);
}
return userRepository.save(user);
}
public UserDTO getUserById(Long id) {
User found = userRepository.findById(id).get();
return modelMapper.map(found, UserDTO.class);
}
// .....
Which I have seen used in some bidirectional relationships
User Entity
#Entity
#Table(name = "Users")
#Getter #Setter #ToString #NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", updatable = false, nullable = false)
private Long id;
private String firstName;
private String lastName;
private int age;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<EmailAddress> emailAddresses;
Email Address Entity
#Entity
#Table(name = "Email")
#Getter #Setter #ToString #NoArgsConstructor
public class EmailAddress {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="email_id", updatable = false, nullable = false)
private Long emailId;
private String email;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST )
#JoinColumn(name = "user_id", nullable = false)
#JsonIgnore
private User user;
Is there a better way to set up the Join relationship?
Sample POST Request
{"firstName":"Joe", "lastName":"Bloggs", "age": 30, "emailAddresses" : [ "joe-private#email.com" , "joe-work#email.com" ] }
I guess you need to associate this email with a user as well, not just set user to email entity.
public void persistUser(EmailAddress emailAddress) {
// set this email to the user
// if email EmailAddresses list is null you might need to init it first
this.getEmailAddresses().add(emailAddress);
emailAddress.setUser(this);
}
Firstly, I believe that a method persistUser should not be a part of a service layer - due to its implementation it mostly like a Domain layer method that should be implemented within a User entity class.
Secondly, since it's a POST method you shouldn't care of emails existence - you are adding a new user with a new set of emails
Closer to a question, I'd suggest you to try this:
public class UserService {
/************/
#Autowired
private UserManager userManager;
public void addUser(UserModel model) {
User user = new User(model);
List<EmailAddress> emails = model.getEmailAddresses().stream().map(EmailAddress::new).collect(Collectors.toList());
user.setEmailAddresses(emails);
userManager.saveUser(user);
}
}
and at the User add this:
public void setEmailAddresses(List<EmailAddress> emails) {
emails.forEach(item -> item.setUser(this));
this.emailAddresses = emails;
}
And don't forget to implement constructors for entities with model paremeters

Do I need to set a childs parent?

I am having an issue where the Domain (child) is not getting the Business (parents) id. I have the business created and the domain but the domain does not have the business_id. Below is what I was doing originally. I then made a test path that I called which gave the same behavior unless I used the setBusiness method of the domain. Is this the expectation? Do I need to loop over the domains that are passed from a client and set the business?
Test
public void test() {
Business b = new Business();
b.setName("Test Test");
Domain d1 = new Domain();
d1.setName("Domain 1");
d1.setBusiness(b);
em.persist(b);
em.flush();
}
Code
#Entity
#Table(name = "Business")
public class Business {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy="business", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Domain> domains = new ArrayList<>();
//...getters and setters
}
-
#Entity
#Table(name = "Domain")
public class Domain {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "business_id", nullable = false)
private Business business;
//...getters and setters
}
I was calling a rest service using the below JSON which then persisted the business.
{
"name": "Business Test ",
"domains": [{"name": "test domain"}]
}
#Path("business")
public class BusinesResouce {
#EJB
BusinessService service;
#Path("create")
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public void create(Business entity) {
service.persist(entity);
}
//other paths
}
-
#Stateless
public class BusinessService {
private static final String PERSISTENCE_UNIT_NAME = "edc";
#PersistenceContext(unitName = PERSISTENCE_UNIT_NAME)
private EntityManager em;
public void persist(Business entity) {
em.persist(entity);
//Do I really need to loop entity.getDomains and setBusiness?
em.flush();
}
}

JPArepository method with ManyToOne not return related data

I have two entities using a OneToMany and ManyToOne relationship:
#Entity
#Table(name = TableName.PERSON)
public class Person {
#Id
#Column(name = FieldName.DB_KEY)
public String dbKey;
#Column(name = FieldName.ENTITY_ID)
public String entityId;
#Column(name = FieldName.SORT_KEY)
public String sortKey;
#JoinColumn(name = FieldName.ENTITY_ID, referencedColumnName =
FieldName.ENTITY_ID, insertable = false, updatable = false)
#ManyToOne(fetch = FetchType.EAGER)
#JoinFetch(value = JoinFetchType.INNER)
public WLEntity entity;
}
#Entity
#Table(name = TableName.WL_ENTITY)
public class WLEntity {
#Id
#Column(name = FieldName.ENTITY_ID)
public String entityId;
#Column(name = FieldName.HAS_ADDRESS)
public boolean hasAddress;
#OneToMany(mappedBy = "entity", targetEntity = PersonIndex.class, cascade = CascadeType.ALL)
public List<Person> persons;
}
And a JPA-Repository defining one findBy... Method:
#Repository
public interface PersonRepository extends JpaRepository<Person, String> {
List<Person> findBySortKeyStartingWith(String sortKey);
}
If I call this method I can see in the console:
SELECT t1.DB_KEY, t1.ENTITY_ID, t1.SORT_KEY, t0.ENTITY_ID, t0.HAS_ADDRESS FROM WL_ENTITY t0, PERSON t1 WHERE (t1.SORT_KEY LIKE ? AND (t0.ENTITY_ID = t1.ENTITY_ID))
So the join I want is correct executed, but in the returned data the entity field is still null but all other fields are filled:
List<Person> persons = personRepository.findBySortKeyStartingWith("Sort");
Person person = persons.get(0);
person.entity == null but person.entityId is correctly filled.
So what I have to do, to get person.entity filled?
I use spring boot with eclipselink jpa 2.6.4