I am facing issues while implemeting #Reference in Spring Boot + Spring Data Redis. Address is a List in Employee and when I saved the office and home address and I was expecting the data to be saved with the Employee. But data did not get saved and hence unable to search the Address using street.
Employee.java
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("employees")
public class Employee {
#Id #Indexed
private String id;
private String firstName;
private String lastName;
#Reference
private List<Address> addresses;
}
Address.java
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("address")
public class Address {
#Id
private String id;
#Indexed
private String street;
private String city;
}
Test class
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
public class EmployeeAdressTest extends RepositoryTestSupport{
#Autowired private EmployeeRepository employeeRepository;
#Before
public void setUp() throws JsonProcessingException {
Address home = Address.builder().street("ABC Street").city("Pune").build();
Address offc = Address.builder().street("XYZ Street").city("Pune").build();
Employee employee1 = Employee.builder().firstName("Raj").lastName("Kumar").addresses(Arrays.asList(home, offc)).build();
employeeRepository.save(employee1);
List<Employee> employees = employeeRepository.findByAddresses_Street("XYZ Street");
System.out.println("EMPLOYEE = "+employees);
}
#Test
public void test() {
}
}
Spring Doc:
8.8. Persisting References
Marking properties with #Reference allows storing a simple key reference instead of copying values into the hash itself. On loading from Redis, references are resolved automatically and mapped back into the object, as shown in the following example:
Example 30. Sample Property Reference
_class = org.example.Person
id = e2c7dcee-b8cd-4424-883e-736ce564363e
firstname = rand
lastname = al’thor
mother = people:a9d4b3a0-50d3-4538-a2fc-f7fc2581ee56
Reference stores the whole key (keyspace:id) of the referenced object.
?
Spring Data Redis requires you to save the objects stored in home and office separately from the referencing object employee1.
This is (now) stated in the official documentation at the very end of chapter 8.8: https://docs.spring.io/spring-data-redis/docs/current/reference/html/#redis.repositories.references
So if you save home and office to the database before saving employee1 you should be fine.
The same btw holds valid for updates you make to referenced objects later on. Just saving the referencing object alone does not save the updates on the referenced objects.
Related
Iam building a simple Spring Boot app, with 2 entities:
- Student model
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String password;
private boolean active;
private Date dob;
private String roles;
#ManyToOne
private Training training;
}
- Training model
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Training {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int duration;
#OneToMany(mappedBy = "training")
#JsonIgnore
private Collection<Student> students;
}
EDIT
I run the app by adding 2 resources in the db:
public static void main(String[] args) {
SpringApplication.run(MsSchoolingSbApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Training t1=trainingRepo.save(new Training(null,"php", 20, null));
Training t2=trainingRepo.save(new Training(null,"java", 20, null));
Student st=new Student(null, "XXXX", "ZZZZ", true,new Date(),"ADMIN",t1);
Student st2=new Student(null, "XXXXX2", "ZZZZZ2", true,new Date(),"USER",t2);
studentRepo.save(st);
studentRepo.save(st2);
}
END EDIT
EDIT 2
- StudentRepo
#RepositoryRestController
public interface StudentRepo extends JpaRepository<Student, Long>{
public List<Student> findByNameStartsWith(String name);
Optional<Student> findByName(String name);
}
- TrainingRepo
#RepositoryRestController
public interface TrainingRepo extends JpaRepository<Training, Long> {
}
END EDIT 2
i've tried to put fetch = FetchType.EAGER or LAZY, i've also added #JsonIgnore but as soon as i fill the db with new data (trainings and students) and run the app, i get this message:
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.schooling.models.Training.students, could not initialize proxy - no Session
What am i doing wrong ?
The problem you got must have related to how you use those 2 entities so you need to provide more information about how you use it.
You might want to look out for your problem in this tutorial: https://www.baeldung.com/hibernate-initialize-proxy-exception
Do not use Lombok's #Data annotation on #Entity classes.
Reason: #Data generates hashcode(), equals() and toString() methods that use the generated getters. Using the getter means of course fetching new data even if the property was marked with FetchType=LAZY.
Somewhere along the way hibernate tries to log the data with toString() and it crashes
EDIT
you can exclude the relation from the toString method by adding, for example in my case:
#ToString(exclude = {"students"})
I want to be able to create a new account for my application. I have an account class which represents one entity and another class that represents personal information of the account. In order to create the new account and have it be in the database I want to add some information into the account table and some information into the PersonalInfo table as detailed in the classes below. How do I do this with a CrudRespository interface. As I understand it, the crudrepository can interact with one table in the database. In my example that would be Accounts. This is fine because most of my checking and communicating will be with the accounts table. But for when I am creating a new account I need to add the data that will be given into two tables. Do I have to make manual queries and add it as a method in there?
#Entity
#Component
public class Account {
#Id
private int accountNum;
private String accountType;
private int accountBalance;
private String accountStatus;
#Entity
#Component
public class PersonalInfo {
#Id
private int accountNum;
private String firstName;
private String lastName;
private String SSN;
private String streetName;
private String city;
private String state;
private String zipcode;
#RepositoryRestResource(collectionResourceRel="accounts",path="accounts")
public interface AccountsDB extends CrudRepository<Account, Integer>{
}
Just create a repository for PersonalInfo and invoke two save() methods (of the two different repositories respectively) with the two created entities respectively.
Just make sure to set the identical ids (accountNum) for these two entities.
Or, you could create a service to do it for you, like so:
public interface AccountAndPersonalInfoService {
void save(Account account, PersonalInfo personalInfo);
}
#Service
public class AccountAndPersonalInfoServiceImpl implements AccountAndPersonalInfoService {
#Autowired
private AccountsDB accountsDB;
#Autowired
private PersonalInfoDB personalInfoDB;
#Override
void save(Account account, PersonalInfo personalInfo) {
if (account.getAccountNum() == personalInfo.getAccountNum()) {
accountsDB.save(account);
personalInfoDB.save(personalInfo);
} else throw new IllegalArgumentException("The ids of the entities do not match.");
}
}
I am using spring-data-redis to communicate with database.
I have entity class like below
#RedisHash(value = "employee")
public class Employee
{
#Id
private long id;
#Indexed
private String name;
#Indexed
private int age;
private Address address;
...... ...... ......
}
I want to filter the employees based on age group. For example, age lesser than 35 (age<35). How to achieve this in below repository?
#Repository
public interface EmployeeRepo extends CrudRepository<Employee, Long>
{
public Employee findByName(String name);
}
I dont prefer to load complete data from table and do search using any loop/stream.
I don't think it does. I tried to implement what you tried to do using
#Index
long lastUpdatedOn;
when I checked the redis it gives a key entity:lastUpdatedOn:160.... and I tried searching for it using ZRANGE which gives no results.
I started using spring data redis in my project for temporary storing some data. Redis is new for me, I've never worked something similar to redis before (Key-Value).
So, traditionally I created repository via extending CrudRepository and my #RedisHash is:
#Data
#NoArgsConstructor
#AllArgsConstructor
#RedisHash(value = "employee", timeToLive = 100)
public class RedisEmployee implements Serializable {
#Id
private String id;
#Indexed
private Long employeeId;
private String fullName;
#Indexed
private String date;
#Indexed
private String companyName;
private String phone;
}
So it works fine but I noticed something strange for me, it's result when
I watch GUI.
This is all data when I save with CrudRepository only one "entity"
So, Look how much rows, I just save 1 #RedisHash value, it could be because of #Indexed annotation but anyway it looks very strange for me.
P.S.
I noticed that without #Indexed it's impossible to find anything, for example:
#Repository
public interface RedisEmployeeRepository extends CrudRepository<RedisEmployee, String> {
RedisEmployee findByDateAndCompanyNameAndEmployeeId(String date, String companyName, Long employeeId);
}
so, findByDateAndCompanyNameAndEmployeeId will not return result if I don't have all fields #Indexed. Can't understand it is proper or not.
I have a logic that saves some data and I use spring boot + spring data jpa.
Now, I have to save one object, and after moment, I have to save another objeect.
those of object consists of three primary key properties.
- partCode, setCode, itemCode.
let's say first object has a toString() returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0021, qty=1.0, sortNo=2, item=null)
and the second object has a toString returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0031, qty=1.0, sortNo=2, item=null)
there is a difference on itemCode value, and itemCode property is belonged to primary key, so the two objects are different each other.
but in my case, when I run the program, the webapp saves first object, and updates first object with second object value, not saving objects seperately.
(above image contains different values from this post question)
Here is my entity information:
/**
* The persistent class for the set_item database table.
*
*/
#Data
#DynamicInsert
#DynamicUpdate
#Entity
#ToString(includeFieldNames=true)
#Table(name="set_item")
#IdClass(SetGroupId.class)
public class SetItem extends BasicJpaModel<SetItemId> {
private static final long serialVersionUID = 1L;
#Id
#Column(name="PART_CODE")
private String partCode;
#Id
#Column(name="SET_CODE")
private String setCode;
#Id
#Column(name="ITEM_CODE")
private String itemCode;
private Double qty;
#Column(name="SORT_NO")
private int sortNo;
#Override
public SetItemId getId() {
if(BooleanUtils.ifNull(partCode, setCode, itemCode)){
return null;
}
return SetItemId.of(partCode, setCode, itemCode);
}
#ManyToMany(fetch=FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name="PART_CODE", referencedColumnName="PART_CODE", insertable=false, updatable=false)
, #JoinColumn(name="ITEM_CODE", referencedColumnName="ITEM_CODE", insertable=false, updatable=false)
})
private List<Item> item;
}
So the question is,
how do I save objects separately which the objects' composite primary keys are partially same amongst them.
EDIT:
The entity extends below class:
#Setter
#Getter
#MappedSuperclass
#DynamicInsert
#DynamicUpdate
public abstract class BasicJpaModel<PK extends Serializable> implements Persistable<PK>, Serializable {
#Override
#JsonIgnore
public boolean isNew() {
return null == getId();
}
}
EDIT again: embeddable class.
after soneone points out embeddable class, I noticed there are only just two properties, it should be three of it. thank you.
#Data
#NoArgsConstructor
#RequiredArgsConstructor(staticName="of")
#Embeddable
public class SetGroupId implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#NonNull
private String partCode;
#NonNull
private String setCode;
}
Check howto use #EmbeddedId & #Embeddable (update you might need to use AttributeOverrides in id field, not sure if Columns in #Embeddable works).
You could create class annotated #Embeddable and add all those three ID fields there.
#Embeddable
public class MyId {
private String partCode;
private String setCode;
private String itemCode;
}
Add needed getters & setters.
Then set in class SetItem this class to be the id like `#EmbeddedId´.
public class SetItem {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="partCode",
column=#Column(name="PART_CODE")),
#AttributeOverride(name="setCode",
column=#Column(name="SET_CODE"))
#AttributeOverride(name="itemCode",
column=#Column(name="ITEM_CODE"))
})
MyId id;
Check also Which annotation should I use: #IdClass or #EmbeddedId
Be sure to implement equals and hashCode in SetGroupId.
Can you provide that class?