I have an API developed on Java using Spring Boot and Spring Data. I'm having a ClassCastException error when I try to get one entity using the method findById(id) from JPARepository
The exception received is:
java.lang.ClassCastException: class com.sun.proxy.$Proxy83 cannot be cast to class package.repository.FAQRepository (com.sun.proxy.$Proxy83 and package.repository.FAQRepository are in unnamed module of loader 'app')
I received it when I try to call to JPARepository.findById(id) However I'm getting the same error on other places calling other spring-data-jpa methods like reposory.save(Entity).
FAQ faq = this.repository.findById(updateFAQ.getId()).orElseThrow(() -> new NotFoundEntityException("FAQ not found"));
FAQsRepoitory:
#Repository
public interface FAQRepository extends GenericRepository<FAQ> {
List<FAQ> findByOperative(Operative operative);
}
GenericRepository:
#Repository
public interface GenericRepository<Entity extends GenericPersistentEntity> extends JpaRepository<Entity, Long> {
}
My entity:
#Entity
#Table(name = "faqs")
#Getter
#Setter
#SQLDelete(sql = "UPDATE faqs SET deleted_date=NOW() WHERE id=?")
public class FAQ extends GenericPersistentEntity {
#Lob
private String question;
#Lob
private String answer;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "operative_id")
private Operative operative;
}
Superclass entity:
#MappedSuperclass
#Where(clause = "deleted_date is null")
public abstract class GenericPersistentEntity implements GenericPersistentInterface {
#Getter
#Setter
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "creation_date", nullable = false)
private Date creationDate;
#UpdateTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_update_date")
private Date updateDate;
#Column(name = "deleted_date")
private Date deletedDate;
}
Any help will be appreciated. Thanks.
Related
I'm trying to build a little project-management tool as my first Spring Boot / JPA / H2 / REST Application using lombok annotations for avoiding boilerplate-code. I followed several promising tutorials. But I'm failing at the very end, when I try to intantiate some dummy data to test the database and start the service.
Till now it had two tables: "T_PROJECT" & "T_EMPLOYEE"
But I also want to be able to visualize, in which period an employee works for a specific project. So I need a third table "T_EMPLOYEE_ACTIVITY" with two extra columns: "START_DATE" & END_DATE".
I made an
ER-Diagram that should help to understand how these tables must work together.
I found already this one here:
JPA 2.0 many-to-many with extra column
... and tried to build it the same way:
The Project entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "T_PROJECT")
public class Project implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "PROJECT_ID")
private Long id;
private String name;
#Column(name = "START_DATE")
private String startDate;
#Column(name = "END_DATE")
private String endDate;
private Status status;
#OneToMany(mappedBy = "project")
#Column(name = "EMPLOYEE_ACTIVITIES")
private Set<EmployeeActivity> employeeActivities;
}
The Employee entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "T_EMPLOYEE")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private Long id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
private String role;
#Column(name = "HOURS_PER_WEEK")
private BigDecimal hoursPerWeek;
#OneToMany(mappedBy = "employee")
#Column(name = "EMPLOYEE_ACTIVITIES")
private Set<EmployeeActivity> employeeActivities;
}
EmployeeActivity entity:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "T_EMPLOYEE_ACTIVITY")
public class EmployeeActivity implements Serializable {
#Id
#Column(name = "EMPLOYEE_ACTIVITY_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "PROJECT_ID")
private Project project;
#ManyToOne
#JoinColumn(name = "EMPLOYEE_ID")
private Employee employee;
#Column(name = "START_DATE")
private String startDate;
#Column(name = "END_DATE")
private String endDate;
}
In the Application.java (with main & run method), I tried to intantiate it like this and failed:
#Override
public void run(String... args) throws Exception {
Project project = new Project(null, "BERLIN_AIRPORT", "2006-05-01", "2020-10-31", Status.COMPLETED, Set.of( ??? ));
projectRepository.save(project);
Employee employee = new Employee(null, "Jim", "Beam", "Architect", BigDecimal.valueOf(40d), Set.of( ??? ));
employeeRepository.save(employee);
EmployeeActivity employeeActivity = new EmployeeActivity();
employeeActivity.setProject(project);
employeeActivity.setEmployee(employee);
employeeActivity.setStartDate("2006.05.01");
employeeActivity.setEndDate("2010.12.12");
employeeActivityRepository.save(employeeActivity);
}
So both - Project and Employee - have an attribute "employeeActivities", that needs some value, when I make a new Object.
But at this point, there is no EmployeeActivity-Object that i could use.
How do I manage this?
Thanks a lot & have nice day!
NicerDicer
I meanwhile found the solution. The problem was, that I tried to use the AllArgsConstructor that has been generated via lombok.
The AllArgsConstructor expects of course all attributes that I declared in the entitties.
The solution is to use setters (in my case auto-generated by the lombok #Data annotation) and to not set the id and employeeActivities from project & employee.
(Alternatively you can of course write your own constructor.)
#Override
public void run(String... args) throws Exception {
Project project_1 = new Project();
project_1.setName("BERLIN_AIRPORT");
project_1.setStartDate("2006-05-01");
project_1.setEndDate("2020-10-31");
project_1.setStatus(Status.COMPLETED);
projectRepository.save(project_1);
Employee employee_1 = new Employee();
employee_1.setFirstName("Jim");
employee_1.setLastName("Beam");
employee_1.setRole("Architect");
employee_1.setHoursPerWeek(BigDecimal.valueOf(40d));
employeeRepository.save(employee_1);
EmployeeActivity employeeActivity = new EmployeeActivity();
employeeActivity.setProject(project_1);
employeeActivity.setEmployee(employee_1);
employeeActivity.setStartDate("2019-05-01");
employeeActivity.setEndDate("2022-12-12");
employeeActivityRepository.save(employeeActivity);
}
I need to get all the information about the ticket in one request, also the name, author, and year of the book. I have implemented this :
I create interface TicketWithBookView
public interface TicketWithBookView {
Date getGiveAway();
Long getReaderId();
Date getTake();
interface Book {
String getAuthor();
String getName();
Integer getYearCreation();
}
}
My entities TicketEntity
#Data
#Entity
#Table(name = "ticket")
#NoArgsConstructor
#AllArgsConstructor
public class TicketEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private Long readerId;
#Column(nullable = false)
private Long bookId;
#Column(nullable = false)
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date take;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private Date giveAway;
}
And second entity BookEntity;
#Entity
#Data
#NoArgsConstructor
#Table(name = "book")
public class BookEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String author;
private Integer yearCreation;
private Integer count;
}
And repository
#Repository
public interface TicketRepository extends CrudRepository<TicketEntity, Long> {
List<TicketWithBookView> findAllByGiveAwayIsNullAndTakeIsNotNull();
}
No way) Projections are used to select data from a query, and not to obtain data from other tables.
You can upload data from another table and create a new model in the service.
probably problem is with AAnd in method name
findAllByGiveAwayIsNullAAndAndTakeIsNotNull
add error message that you get, It would be easier to find problem
I'm trying to build build service, which saves object with sub-objects, but getting error. In result object data fields saved, but sub-object not.
I have the next object. The main is Order and sub-object is Partner:
#Getter
#Setter
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private int orderId;
#OneToMany(mappedBy = "order", fetch = FetchType.EAGER,
cascade = CascadeType.ALL)
private Set<Partner> partners;
}
#Getter
#Setter
#Entity
#Table(name = "partners")
public class Partner implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "partner_id")
private int id;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
I use standard embedded method "save" from Spring Jpa Repository:
#Repository
public interface OrdersRepository extends JpaRepository<Order, Integer> {
}
and service, which call this Repository:
#Service
public class OrdersServiceImpl implements OrdersService {
#Autowired
private OrdersRepository repository;
#Override
public Order save(Order order) {
return repository.save(order);
}
}
Does someone have an idea why Partners are not saved?
Thanks a lot!
Because the relationship owner is Partner, so that you need to save the Order first. Or you can put cascade = CascadeType.PERSIST on private Order order;
I currently have an Entity as below:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long productId;
private String productImage;
private String productTitle;
private String productDescription;
private Integer productPrice;
private Date createdAt;
private Date updatedAt;
Upon creation of this object, the value of createdAt and updatedAt shows null in the database and was wondering how I can implement code so that createdAt and updateAt automatically gets inserted?
My post method is as below:
#PostMapping("/products")
public ProductResponse createProduct(#Validated #RequestBody ProductForm productForm) {
Product product = productForm.asProduct();
Product createdProduct = productRepository.save(product);
return new ProductResponse(createdProduct, "Product created");
}
JPA
There isn't anything as convenient as annotating the Timestamp field directly but you could use the #PrePersist, #PreUpdate annotations and with little effort achieve the same results.
Hibernate
#CreationTimestamp - Documentation
#UpdateTimestamp - Documentation
Spring Data JPA
#CreatedDate - Documentation
#LastModifiedDate - Documentation
Extend the following abstract class in your entity:
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
public abstract class DateAudit implements Serializable {
#CreatedDate
#Column(name = "created_at", nullable = false, updatable = false)
private Date createdAt;
#LastModifiedDate
#Column(name = "updated_at")
private LocalDateTime updatedAt;
}
Don't forget to enable JPA Auditing feature using #EnableJpaAuditing
Read this: https://docs.spring.io/spring-data/jpa/docs/1.7.0.DATAJPA-580-SNAPSHOT/reference/html/auditing.html
With the mix of #dimitrisli and #buddha answers, something pretty clean is
#Data
#MappedSuperclass
public abstract class BaseEntity {
#Column(updatable = false)
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
}
And now you all your entity can extend that class like so
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
public class User extends BaseEntity {
#Id
#GeneratedValue
public UUID id;
public String userName;
public String email;
public String firstName;
public String lastName;
}
Note that you might not need #Data & #EqualsAndHashCode annotations from lombok as it generate getter/setter
You can create a BaseEntity. Each entity extends the BaseEntity. In the Base entity ,it will set the time automatically
#Data
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class BaseEntity implements Serializable {
#Id
#Column(name = "Id")
private String id;
#Column(name = "deleted", columnDefinition = "Bit(1) default false")
private boolean deleted = false;
#Column(name = "DataChange_CreatedBy", nullable = false)
private String dataChangeCreatedBy;
#Column(name = "DataChange_CreatedTime", nullable = false)
private Date dataChangeCreatedTime;
#Column(name = "DataChange_LastModifiedBy")
private String dataChangeLastModifiedBy;
#Column(name = "DataChange_LastTime")
private Date dataChangeLastModifiedTime;
#PrePersist
protected void prePersist() {
if (this.dataChangeCreatedTime == null) dataChangeCreatedTime = new Date();
if (this.dataChangeLastModifiedTime == null) dataChangeLastModifiedTime = new Date();
}
#PreUpdate
protected void preUpdate() {
this.dataChangeLastModifiedTime = new Date();
}
#PreRemove
protected void preRemove() {
this.dataChangeLastModifiedTime = new Date();
}
}
I have a problem, that a Date-Type from an #Embeddable-Class will not mapped as a Date-Type in the MySQL-Database. Instead it gets mapped as VARCHAR(255). With normal #Entity-Classes it is working properly.
I am using JPA 2.1 with EclipseLink 2.5.
I have an #Embeddable-Class like this:
#Embeddable
public class AbsencePeriod implements Serializable {
#Getter
#Setter
#Column(name = "\"START\"")
#Temporal(TemporalType.DATE)
private Date start;
#Getter
#Setter
#Column(name = "\"END\"")
#Temporal(TemporalType.DATE)
private Date end;
public AbsencePeriod() {
}
}
I include it at another class like this:
#Entity
public Person extends BaseEntity implements Serializable {
#ElementCollection
#CollectionTable(
name = "_PERSON_ABSENCEPERIODS",
joinColumns = #JoinColumn(name = "PERSON_ID")
)
#Getter
#Setter
private List<AbsencePeriod> absencePeriods;
}
I have also a workaround for this, but I want to know, why the wrong mapping happens. Workaround looks like setting the #ColumnDefinition manual:
#Embeddable
public class AbsencePeriod implements Serializable {
#Getter
#Setter
#Column(name = "\"START\"")
#Basic
#Column(columnDefinition = "TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
private Date start;
}