ElementCollection and "failed to lazily initialize a collection of role" exception - spring

I'm very new to Spring and I'm trying to figure out how to use #ElementCollection.
I have the following classes:
#Embeddable
public class Phone {
private String type;
private String areaCode;
#Column(name="P_NUMBER")
private String number;
public Phone() {
}
public Phone(String type, String areaCode, String number) {
super();
this.type = type;
this.areaCode = areaCode;
this.number = number;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getAreaCode() {
return areaCode;
}
public void setAreaCode(String areaCode) {
this.areaCode = areaCode;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "EMP_ID")
private long id;
#ElementCollection//(fetch=FetchType.EAGER)
#CollectionTable(name = "PHONE", joinColumns = #JoinColumn(name = "OWNER_ID"))
private List<Phone> phones = new ArrayList<Phone>();;
public Employee() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public List<Phone> getPhones() {
return phones;
}
public void setPhones(List<Phone> phones) {
this.phones = phones;
}
}
Repository:
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, Long>{
public Employee findById(long id);
}
Then I use it in main method:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
EmployeeRepository repository = context.getBean(EmployeeRepository.class);
Phone phone = new Phone("work", "613", "494-1234");
Employee emp = new Employee();
emp.getPhones().add(phone);
repository.save(emp);
emp = repository.findById(1);
for (Phone p : emp.getPhones()) {
System.out.println(p.getNumber());
}
context.close();
}
It throws exception (when emp.getPhones() is called): Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: elcol.repository.Employee.phones, could not initialize proxy - no Session
If I add (fetch=FetchType.EAGER) to #ElementCollection annotation(commented in the code above in Employee class) - everything is ok.
How can I fix this without FetchType.EAGER?

In findById(long id) implementation, add this Hibernate.initialize(emp.getPhones()).
Your repository service should return all the data you will need already initialized so the client that calls the service stays independent of it. In short, If you don't need employees phones on the client side, don't initialize it. If you do need it - initialize it.
EDIT
With spring data you obviously don't have the implementation, so you can specify the query which will be used, and fetch the data in the query (the question is tagged with jpa so I guess you can use JpaRepository)
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long>{
#Query("SELECT e FROM Employee e JOIN FETCH e.phones WHERE e.id = (:id)")
public Employee findById(long id);
}

Related

Parameter value [multiVLANSupport] did not match expected type [java.util.List (n/a)]

I have created an entity class that has a column which uses Attribute Converter of JPA:
#Convert(converter = StringListConverter.class)
private List<String> functionSpecificationLabel;
The converter class is :
#Converter
public class StringListConverter implements AttributeConverter<List<String>, String> {
#Override
public String convertToDatabaseColumn(List<String> list) {
return String.join(",", list);
}
#Override
public List<String> convertToEntityAttribute(String joined) {
return new ArrayList<>(Arrays.asList(joined.split(",")));
}
}
The expected values of the column in the Tables are like
functionSpecificationLabel
multiVLANSupport,telepresence,csaid
Now I need to return the rows that have multiVLANSupport,telepresence,csaid as value in functionSpecificationLabel column.
My Query in the repository for the same is :
#Query("Select pd from ProductDetailsEntity pd where pd.functionSpecificationLabel in (:labels)")
Optional<ProductDetailsEntity> findByFunctionSpecificationLabel(#Param("labels") final List<String> labels);
Now I face the issue as :
Parameter value [multiVLANSupport] did not match expected type [java.util.List (n/a)]
I am not exactly sure if this is even possible, here is how i have implemented to store list of values in an entity class using #ElementCollection You can read more about it here https://thorben-janssen.com/hibernate-tips-query-elementcollection/
A good discussion can be found here How to persist a property of type List<String> in JPA?. My suggestion is to avoid storing any values in db based on a delimiter.
Ideally, when storing such labels it is better to map them using OneToMany relationship. Also note that this will create an additional table in this case animal_labels.
Answer 1
Repository
#Repository
public interface AnimalRepository extends JpaRepository<Animal, UUID> {
List<Animal> findDistinctAnimalsByLabelsIsIn(List<String> cute);
}
Entity class
#Entity
#Table(name = "animal")
public class Animal {
#Id
#GeneratedValue
#Type(type = "uuid-char")
private UUID id;
private String name;
#ElementCollection(targetClass = String.class)
private List<String> labels;
public Animal() {
}
public Animal(String name, List<String> labels) {
this.name = name;
this.labels = labels;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getLabels() {
return labels;
}
public void setLabels(List<String> labels) {
this.labels = labels;
}
}
Test:
#ExtendWith(SpringExtension.class)
#Transactional
#SpringBootTest(classes = TestApplication.class)
class CustomConverterTest {
#Autowired
private EntityManager entityManager;
#Autowired
private AnimalRepository animalRepository;
#Test
void customLabelConverter() {
Animal puppy = new Animal("Puppy", Arrays.asList("cute", "intelligent", "spy"));
Animal meow = new Animal("Cat", Arrays.asList("cute", "intelligent"));
entityManager.persist(puppy);
entityManager.persist(meow);
List<Animal> animalWithCutelabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("cute"));
List<Animal> animalWithSpylabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("spy"));
List<Animal> animalWithCuteAndSpylabels = animalRepository.findDistinctAnimalsByLabelsIsIn(Arrays.asList("cute", "spy"));
Assertions.assertEquals(2, animalWithCutelabels.size());
Assertions.assertEquals(1, animalWithSpylabels.size());
Assertions.assertEquals(2, animalWithCuteAndSpylabels.size());
}
}
Answer 2
If you do have any choice but to only go with the comma separated values then please find answer below for this approach:
Repository(since this is a string we cannot use list like in)
#Repository
public interface AnimalRepository extends JpaRepository<Animal, UUID> {
// Also note that the query goes as string and not list
List<Animal> findAllByLabelsContaining(String labels);
}
Test:
#Test
void customLabelConverter() {
Animal puppy = new Animal("Puppy", String.join(",", Arrays.asList("cute", "intelligent", "spy")));
Animal meow = new Animal("Cat", String.join(",", Arrays.asList("cute", "intelligent")));
entityManager.persist(puppy);
entityManager.persist(meow);
List<Animal> animalWithCutelabels = animalRepository.findAllByLabelsContaining(String.join(",", Arrays.asList("cute")));
List<Animal> animalWithSpylabels = animalRepository.findAllByLabelsContaining(String.join(",", Arrays.asList("spy")));
Assertions.assertEquals(2, animalWithCutelabels.size());
Assertions.assertEquals(1, animalWithSpylabels.size());
}
Entity:
#Entity
#Table(name = "animal")
public class Animal {
#Id
#GeneratedValue
#Type(type = "uuid-char")
private UUID id;
#Column
private String name;
#Column
private String labels;
public Animal() {
}
public Animal(String name, String labels) {
this.name = name;
this.labels = labels;
}
public UUID getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getLabels() {
if (StringUtils.isEmpty(labels)) return Collections.emptyList();
return new ArrayList<>(Arrays.asList(labels.split(AnimalLabelsConverter.DELIMITER_COMMA)));
}
public void setLabels(List<String> labels) {
if (CollectionUtils.isEmpty(labels)) {
this.labels = "";
} else {
this.labels = String.join(AnimalLabelsConverter.DELIMITER_COMMA, labels);
}
}
#Converter
public static class AnimalLabelsConverter implements AttributeConverter<List<String>, String> {
private static final String DELIMITER_COMMA = ",";
#Override
public String convertToDatabaseColumn(List<String> labels) {
if (CollectionUtils.isEmpty(labels)) return "";
return String.join(DELIMITER_COMMA, labels);
}
#Override
public List<String> convertToEntityAttribute(String dbData) {
if (StringUtils.isEmpty(dbData)) return Collections.emptyList();
return new ArrayList<>(Arrays.asList(dbData.split(DELIMITER_COMMA)));
}
}
}

No converter found capable of converting from type to type #2

I know it's another similar question, but I can't answer it myself, that's why I'm writing to you for help.
I try to create my own #Query and returns a conversion error on two occasions. My guess is there is a problem with the service, but this is where my knowledge ends.
Here is my code:
Main entity
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String city;
private Date startDate;
private boolean stat;
public User() {
}
public User(String name, String city, Date startDate, boolean stat) {
this.name = name;
this.city = city;
this.startDate = startDate;
this.stat = stat;
}
}
2.Second model
public class UserName {
private String firstname;
public UserName() {
}
public UserName(String firstname) {
this.firstname = firstname;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
3.Third model
public class UserCount {
private String city;
private int count;
public UserCount() {
}
public UserCount(String city, int count) {
this.city = city;
this.count = count;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
Repository
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select p from User p") //1.it's work
List<User> getAll();
#Query("select u from User u where u.name like %?1") //2.it's work
List<User> findByFirstnameEndsWith(String firstname);
#Query("select u.name from User u ") //3. don't work
List<UserName> getNameUsers();
// this SQL working in database console H2
// SELECT city, count(*) FROM USERS WHERE stat = true GROUP BY city
#Query("select u.city, count (u) from User u where u.stat = true group by u.city") //3. don't work
List<UserCount> getOwnQuery();
}
Service
#Service
public class UserService {
#Autowired
private UserRepository repo;
public List<UserName> getN (){
return repo.getNameUsers();
}
public List<UserCount> getC(){
return repo.getOwnQuery();
}
}
Controller
#Controller
public class MyController {
#Autowired
private UserRepository repo;
#Autowired
private UserService repoService;
#GetMapping("/") //1.it's work
ResponseEntity<List<User>> getAllCity(Pageable page){
return ResponseEntity.ok(repo.getAll());
}
#GetMapping("/s") //2.it's work
ResponseEntity<List<User>> getAllUsers(Pageable page){
return ResponseEntity.ok(repo.findByFirstnameEndsWith("Seba"));
}
#GetMapping("/f") ///3.don't work
ResponseEntity<List<UserName> >getUsersName(Pageable page){
return ResponseEntity.ok(repoService.getN());
}
#GetMapping("/c") ///4.don't work
ResponseEntity<List<UserCount> >getUsersCount(Pageable page){
return ResponseEntity.ok(repoService.getC());
}
}
It also adds source code on GitHub
sorry i didn't add the error code
Use constructor with NEW keyword in #Query to get List<UserName>
#Query("select NEW com.sub.model.UserName(u.name) from User u ")
List<UserName> getNameUsers();
And do the same for List<UserCount>
#Query("select NEW com.sub.model.UserCount(u.city, count(u)) from User u where u.stat = true group by u.city")
List<UserCount> getOwnQuery();

null values inserted while auditing

My AuditListener
public class EmployeeAuditListeners {
#PrePersist
public void prePersist(Employee employee){
perform(employee,Action.INSERTED);
}
#PreUpdate
public void preUpdate(Employee employee){
perform(employee,Action.UPDATED);
}
#PreRemove
public void preRemove(Employee employee){
perform(employee,Action.DELETED);
}
#Transactional
public void perform(Employee emp, Action action){
EntityManager em = BeanUtil.getBean(EntityManager.class);
CommonLogs commonLogs = new CommonLogs();
commonLogs.setQuery("new query");
em.persist(commonLogs);
}
}
and My Auditable.class
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {
#CreatedBy
protected U createdBy;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
protected Date createdDate;
#LastModifiedBy
protected U lastModifiedBy;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
protected Date lastModifiedDate;
}
My CommonLogs.class
#Entity
#EntityListeners(AuditingEntityListener.class)
public class CommonLogs extends Auditable<String> {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String query;
public CommonLogs() {
}
public CommonLogs(String query) {
this.query = query;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
}
My Employee.java class
#Entity
#EntityListeners(EmployeeAuditListeners.class)
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String address;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
and I have a simple Rest Controller
#RestController
#RequestMapping("/api")
public class EmployeeController {
#Autowired
private EmployeeRepository employeeRepository;
#PostMapping("/employees")
public Employee createEmployee(#RequestBody Employee employee){
return employeeRepository.save(employee);
}
}
I want to log it on my table (common_logs) every time i perform some crud operations on my Employee Entity.
the above given example is working to some extent as it successfully stores employee and invokes EmployeeAuditListeners.
but now while saving CommongLog entity i expect it's parent class Auditable to automatically insert createdBy, createdDate etc. for now only query and id is inserted on common_logs table and remaining columns are null.
You can review the documentation for Auditing in here.
To enable the automatic Auditing, you must add the annotation #EnableJpaAuditing in your Application class:
#SpringBootApplication
#EnableJpaAuditing
class Application {
static void main(String[] args) {
SpringApplication.run(Application.class, args)
}
}
If you want the fields #CreatedBy and #LastModifiedBy too, you will also need to implement the AuditorAware<T> interface. For example:
class SpringSecurityAuditorAware implements AuditorAware<User> {
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((MyUserDetails) authentication.getPrincipal()).getUser();
}
}

How to combine two DAOs in generic way with only one method in Service layer in Spring Boot

Is it possible to combine two DAOs into one Service method?
I want to create a generic method which will choose correct DAO based on the input parameter. What for now I came up with is the method which will accept Dao from the outside the service object. But this requires to initialize appropriate Dao in the Controller which is a little bit ugly...
Measurement is just an interface for Temperature.java and Humidity.java entities with separate tables on PostgreSQL.
#Service
public class MeasurementService {
#Autowired
private TemperatureDao temperatureDao;
#Autowired
private HumidityDao humidityDao;
public<T extends PagingAndSortingRepository<Measurement, Long>> void insertMeasurementForUser(String username, List<Measurement> measurements, T dao) {
dao.saveAll(measurements);
}
}
TemperatureDao.java
#Repository
public interface TemperatureDao extends PagingAndSortingRepository<Temperature, Long> {
#Query("select u from Temperature u where u.owner = ?1 order by u.id desc")
List<Temperature> findLatestTemperatureForUser(User user, Pageable pageable);
}
HumidityDao.java
#Repository
public interface HumidityDao extends PagingAndSortingRepository<Humidity, Long> {
#Query("select u from Humidity u where u.owner = ?1 order by u.id desc")
List<Humidity> findLatestHumidityForUser(User user, Pageable pageable);
}
Temperature.java
#Entity
#Table(name = "temperature")
public class Temperature implements Measurement {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "th1value")
private Float th1Value;
#Column(name = "timestamp")
#NotNull
private LocalDateTime timestamp;
#ManyToOne
#JoinColumn(name = "user_id")
#NotNull
private User owner;
public Temperature() {
}
public Temperature(Float th1Value, LocalDateTime timestamp, User owner) {
this.th1Value = th1Value;
this.timestamp = timestamp;
this.owner = owner;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
public LocalDateTime getTimestamp() {
return timestamp;
}
#JsonSerialize(using = LocalDateTimeSerializer.class)
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
#Override
public User getOwner() {
return owner;
}
#Override
public void setOwner(User owner) {
this.owner = owner;
}
}
Humidity.java
#Entity
#Table(name = "humidity")
public class Humidity {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "hum1value")
private Float hum1Value;
#Column(name = "timestamp")
#NotNull
private LocalDateTime timestamp;
#ManyToOne
#JoinColumn(name = "user_id")
#NotNull
private User owner;
public Humidity() {
}
public Humidity(Float hum1Value, LocalDateTime timestamp, User owner) {
this.hum1Value = hum1Value;
this.timestamp = timestamp;
this.owner = owner;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
public LocalDateTime getTimestamp() {
return timestamp;
}
#JsonSerialize(using = LocalDateTimeSerializer.class)
public void setTimestamp(LocalDateTime timestamp) {
this.timestamp = timestamp;
}
public User getOwner() {
return owner;
}
public void setOwner(User owner) {
this.owner = owner;
}
}
Any ideas?
You could write a Resolver pattern to return needed dao based on your conditions. You service will use the resolver to get the correct dao.
public HellDao implements BaseDao {
public void save();
}
public ByeDao implements BaseDao {
public void save();
}
public DaoResolver {
#Autowired
private helloDao;
#Autowired
private byeDao;
public BaseDao resolve(Object input) {
//based on input return the correct dao
BaseDao resolvedDao = null;
switch(input.enum) {
case Hello:
resolvedDao = helloDao;
break;
case Hello:
resolvedDao = byeDao;
break;
default:
//decide something for default
}
return resolvedDao;
}
}
public class MyService {
#Autowired
private DaoResolver daoResolver;
public Object doSomething() {
BaseDao dao = daoResolver.resolve(someObject);
//you will get HelloDao or ByeDao based on the input
dao.save();
}
}
You can check for the type of measurements using instanceof so you could do it without generics.
public void insertMeasurementForUser(String username, List<Measurement> measurements) {
if(measurements.get(0) instanceof Temperature)
temperatureDao.saveAll(measurements);
else if(measurements.get(0) instanceof Humidity)
humidityDao.saveAll(measurements);
}

how to Fix spring boot one to many bidirectional infinity loop?

i am try to create a one to many bidirectional mapping using spring boot and spring data jpa please look the below entity
Employer Entity
#Entity
public class Employer
{
private Long id;
private String employerName;
private List<Employee> employees;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getEmployerName()
{
return employerName;
}
public void setEmployerName(String employerName)
{
this.employerName = employerName;
}
#OneToMany(cascade=CascadeType.ALL, mappedBy="employer")
public List<Employee> getEmployees()
{
return employees;
}
public void setEmployees(List<Employee> employees)
{
this.employees = employees;
}
}
Employee Entity
#Entity
public class Employee
{
private Long id;
private String employeeName;
private Employer employer;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
return id;
}
public void setId(Long id)
{
this.id = id;
}
public String getEmployeeName()
{
return employeeName;
}
public void setEmployeeName(String employeeName)
{
this.employeeName = employeeName;
}
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
public Employer getEmployer()
{
return employer;
}
public void setEmployer(Employer employer)
{
this.employer = employer;
}
}
Employer Repo
public interface EmployerServices extends JpaRepository<Employer, Long> {
}
Employee Repo
public interface EmployeeServices extends JpaRepository<Employee, Long> {
}
REST Controller is
#RestController
public class Controller {
#Autowired EmployeeServices employeeServices;
#Autowired EmployerServices employerServices;
#GetMapping("/getempr")
public Object getempr(){
return employerServices.findOne(1L);
}
}
now the problem begin start see my out put
its look like a infighting loop and my server throwing error getOutputStream() has already been called for this response.
I used #JsonBackReference & #JsonManagedReference
annotation but the problem is its working like one to many
{
"id":1,
"employerName":"employer",
"employees":[
{"id":1,"employeeName":"emp1"},
{"id":2,"employeeName":"emp2"}
]
}
if I am trying to get in the concern of many to one like all employee with employer. the output is
[
{
"id":1,
"employeeName":"emp1"
},
{
"id":2,
"employeeName":"emp2"}
]
its not showing me the employer details.
please suggets me guys what i am doing wrong. thanks in advance!!
Instead of using #JsonBackReferenceand #JsonManagedReference try to use annotation #JsonIgnoreProperties:
#JsonIgnoreProperties("employer")
private List<Employee> employees;
#JsonIgnoreProperties("employees")
private Employer employer;
It prevents Jackson from rendering a specified properties of associated objects.
with the JSON its a problem with bi-directional mapping. Use the below properties.
#JsonIgnoreProperties("employer")
#JsonIgnoreProperties("employees")
please keep fetching type as eager.
hope this will work.
You can solve your issue with two modification with annotations.
Employer.class
#Entity
public class Employer {
private Long id;
private String employerName;
#OneToMany(cascade = CascadeType.ALL,
mappedBy = "employer",
orphanRemoval = true)
private List<Employee> employees;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmployerName() {
return employerName;
}
public void setEmployerName(String employerName) {
this.employerName = employerName;
}
public List<Employee> getEmployees() {
return employees;
}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
}
Employee.class
#Entity
public class Employee {
private Long id;
private String employeeName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employer_id")
private Employer employer;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public Employer getEmployer() {
return employer;
}
public void setEmployer(Employer employer) {
this.employer = employer;
}
}
For more information please visit this link.
Change your getEmployer Method like this:
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
public Employer getEmployer()
{
return employer;
}
use
#JsonProperty(access = Access.WRITE_ONLY)
private List<Employee> employees;
So that it will ignore employees while printing to JSON in the response (and thus prevents the looping), but will still consider the JSON data (employee list) you pass in the request body so that it is available for persistence.

Resources