I've started to learn Spring Boot, I've created two entities Invoice and YearDate
When I try to search by year(id), in my log query I got null (see this part of log query)
http://localhost:8080/appapi/invoices/search/findByYearId?year=1
from invoice invoice0_ left outer join year_date yeardate1_ on invoice0_.year_id=yeardate1_.id where yeardate1_.id is null limit?
I'm using Lombok also for getters and setters
Here are all my class entities, SQL tables, and JpaRepository interface :
SQL foreign key :
KEY `fk_year` (`year_id`),
CONSTRAINT `fk_year` FOREIGN KEY (`year_id`) REFERENCES `YearDate` (`id`)
YearDate class :
#Entity
#Table(name="YearDate")
#Data
public class YearDate {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "year_value")
private String yearValue;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "year")
private Set<Invoice> invoices;
}
Invoice class:
#Entity
#Table(name="invoice")
#Data
public class Invoice {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "year_id", nullable = false)
private YearDate year;
#Column(name = "description")
private String description;
}
**And The Invoice Interface:**
#CrossOrigin("http://localhost:4200")
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
Page<Invoice> findByYearId(#RequestParam("year") Long id, Pageable page);
}
Work for me.
try to extract call in rest controller and log params
#GetMapping(value = "/invoices")
public Page<Invoice> getInvoices(#RequestParam("yearId") Long yearId) {
Page<Invoice> byYearId = invoiceRepository.findByYearId(yearId, PageRequest.of(0, 10));
return byYearId;
}
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
Page<Invoice> findByYearId(Long id, Pageable page);
}
#ManyToOne
#JoinColumn(name = "year_id", nullable = false)
#JsonIgnoreProperties("invoices")
private YearDate year;
#OneToMany(mappedBy = "year")
#JsonIgnoreProperties("year")
private Set<Invoice> invoices;
Generated SQL :
Hibernate:
select
invoice0_.id as id1_0_,
invoice0_.description as descript2_0_,
invoice0_.name as name3_0_,
invoice0_.year_id as year_id4_0_
from
invoice invoice0_
left outer join
year_date yeardate1_
on invoice0_.year_id=yeardate1_.id
where
yeardate1_.id=? limit ?
Hibernate:
select
yeardate0_.id as id1_5_0_,
yeardate0_.year_value as year_val2_5_0_
from
year_date yeardate0_
where
yeardate0_.id=?
-> ADD #JsonIgnoreProperties("invoices") and #JsonIgnoreProperties("year") to entities to avoid infinite json recusrsion.
I've disabled Lombok and it works
try pass him a Year object not id is better
#CrossOrigin("http://localhost:4200")
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
Page<Invoice> findByYear(YearDate year, Pageable page);
}
and in your service get this year
for exemple I create a getYears method in service:
#Service
public class YearService{
#Autowired
private YearRepository yearRepository;
#Autowired
private InvoiceRepository invoiceRepository;
getYears(idYear:Long){
YearDate yearParam=yearRepository.findById(id).get();
Page<Invoice> invoices=invoiceRepository.findByYear(yearParam,YourPagination)
}
}
Related
User.java
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_role_id", referencedColumnName = "id")
private UserRole userRole;
}
UserRole.java
#Data
#Entity
#Table(name = "user_roles")
public class UserRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
Client.java
#Data
#Entity
#Table(name = "clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumns({ #JoinColumn(name = "user_id", referencedColumnName = "id"),
#JoinColumn(name = "user_role_id", referencedColumnName = "user_role_id") })
private User user;
}
Error
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.MappingException: Unable to find column with logical name: user_role_id in users
In RDBMS, users.(id, user_role_id) is unique so clients table can refer to that.
Last time, I was using insertable = false, updatable = false on user_role_id, but when I want to add records of new client, I always need to add user_role_id manually user.setUserRoleId(userRole.getId()) after user.setUserRole(userRole) and I think that is bad practice of ORM (it should be added automatically when I set user.setUserRole(userRole))
#Column(name = "user_role_id", insertable = false, updatable = false)
private Integer userRoleId;
What should I do so the relation can be mapped in Spring JPA? and what is the best practice?
In other words, this is also mean how to reference to foreign key generated logical name column?
OK! Please try following configuration:
Below is a important code part and under this link you may find repository with working example
UserRole.java
#Data
#Entity
#Table(name = "user_roles")
public class UserRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "role_id")
private Integer roleId;
}
User.java
#Data
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Integer userId;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_role_id", referencedColumnName = "role_id")
private UserRole userRole;
}
Client.java
#Data
#Entity
#Table(name = "clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "client_id")
private Integer clientId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns(
value = {
#JoinColumn(name = "client_role_id", referencedColumnName = "user_role_id"),
#JoinColumn(name = "client_user_id", referencedColumnName = "user_id"),
}
,
foreignKey = #ForeignKey(
name = "FK_user_with_role",
foreignKeyDefinition = "FOREIGN KEY (client_user_id, client_role_id)\n" +
" REFERENCES users \n" +
" (user_id, user_role_id) \n" +
" ON UPDATE CASCADE\n" +
" ON DELETE CASCADE")
)
private User user;
}
Please note that beside adding a foreignKey in the Client implementation, you MUST keep the sequence of #JoinColum annotations.. I don't know what is the reason behind, but if you flip those lines you'll still get your error as it was before :)
EDIT: I've added another answer which fits best in my opinion. I'm leaving this one as well to see the other steps I tried.
Though the solution is not elegant and not using JPA as requested. Just in case anything in here would be helpful
If I understand the main issue correctly - you want to bind Client entity with Role entity via User entity, by first setting User's Role and then transfer that "property" by using only UserId instead setting additionally RoleId while creating Client.
Basically after playing for a while with your model I think the main issue is to assign data to each other within a #Transactional methods. That seems to be caused ba Lazy fetch strategy.
My proposal for solution that binds all your Entities according expectations differs only from yours with ommiting the RoleId JoinColumn in Clients table. I have checked that when calling a service that would have #Transactional methods, you can assign a Role to the User and User to the Client with simple user.setRole(roleEntity) followed by client.setUser(userEntity).
All the data is then consistent. No need to call further like getters and setters as you mentioned in the second part of your question. Question is if for any reason you need to have RoleId as well in your Clients Table, then this soultion would have to be enhanced by additional column?
UserRole.java
#Entity
#Table(name = "user_roles")
public class UserRole implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "role_id")
private Integer roleId;
//getters and setters and toString
}
User.java
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Integer userId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_role_id", referencedColumnName = "role_id")
private UserRole userRole;;
//getters and setters and toString;
}
Client.java
#Entity
#Table(name = "clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "client_id")
private Integer clientId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "client_user_id", referencedColumnName = "user_id"),
})
private User user;
#Column(name = "client_role_id")
private Integer roleId;
#PrePersist
#PreUpdate
private void prePersist(){
try {
roleId = getUser().getUserRole().getRoleId();
} catch (NullPointerException e){
roleId = null;
}
}
//getters and setters and toString
}
UserService.java
#Service
public class UserService {
UserRepo userRepo;
public UserService(UserRepo userRepo) {
this.userRepo = userRepo;
}
#Transactional
public void save(User user) {
userRepo.save(user);
}
#Transactional
public User getReferenceById(int i) {
return userRepo.getReferenceById(i);
}
}
ClientService.java
#Service
public class ClientService {
private ClientRepo clientRepo;
private UserService userService;
public ClientService(ClientRepo clientRepo, UserService userService) {
this.clientRepo = clientRepo;
this.userService = userService;
}
#Transactional
public Client save(Client client){
return clientRepo.save(client);
}
#Transactional
public Client getReferenceById(int i) {
return clientRepo.getReferenceById(i);
}
#Transactional
public void printClient(Client client){
client = clientRepo.getReferenceById(client.getClientId());
System.out.println(client);
}
#Transactional
public void bindUserToClient(int userId, int clientId) {
Client entity = clientRepo.findById(clientId).orElseGet(Client::new);
entity.setUser(userService.getReferenceById(userId));
}
#Transactional
public void printClient(int i) {
clientRepo.findById(i).ifPresentOrElse(this::printClient, EntityNotFoundException::new);
}
}
This configuration after running this commandLineRunner:
#Configuration
public class Config {
#Bean
#Transactional
public CommandLineRunner commandLineRunner(
#Autowired UserRoleRepo roleRepo,
#Autowired UserService userService,
#Autowired ClientService clientService
) {
return args -> {
for (int i = 0; i < 5; i++) {
roleRepo.save(new UserRole());
}
for (int i = 5; i > 0; i--) {
User user = new User();
user.setUserRole(roleRepo.getReferenceById(i));
userService.save(user);
}
Client client = new Client();
client.setUser(userService.getReferenceById(2));
client = clientService.save(client);
clientService.printClient(client);
client = new Client();
client.setClientId(1);
clientService.printClient(client);
int userId = 5;
clientService.bindUserToClient(userId, 1);
clientService.printClient(1);
};
}
}
gave me correct output in the console:
Client{id=1, user=User{id=2, userRole=UserRole{id=4}}}
Client{id=1, user=User{id=2, userRole=UserRole{id=4}}}
Client{id=1, user=User{id=5, userRole=UserRole{id=1}}}
WORKAROUND
I tried to reach the goal by use of Spring JPA but could'nt.
The workaround that keeps the referential integrity was by creating a constrains through DB like below and add #PrePersist and #PreUpdate annotated method which is updating the client's roleId as intended.
create table clients
(
client_id integer not null,
client_user_id integer,
client_role_id integer,
primary key (client_id)
);
create table user_roles
(
role_id integer generated by default as identity,
primary key (role_id)
);
create table users
(
user_id integer generated by default as identity,
user_role_id integer,
primary key (user_id),
CONSTRAINT User_Role UNIQUE (user_id, user_role_id)
);
alter table users
add constraint FK_role_id foreign key (user_role_id) references user_roles (role_id);
alter table clients
add constraint FK_user_id foreign key (client_user_id, client_role_id) references users (user_id, user_role_id) on update cascade ;
Thanks to that I could for instance update userRole in user entity, and the change was reflected in the clients table as well without any further actions
I am fetching data from FB marketing API and trying to save in DB. I am able to save data in the DB using CrudRepository or JpaRepository -> saveall method, but when trying to fetch the id in response of saveall, I am getting id as null. When I see in the h2-console, able to see the auto increment value after the completion of transaction.
Note: id is not used as primary key #Id. accountId is used as primary key.
Model:
#Entity
#Table(name = "accounts")
#Data
#ToString(onlyExplicitlyIncluded = true)
public class Account implements Serializable{
#JsonIgnore
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(columnDefinition = "integer auto_increment",insertable = false)
private Long id;
#JsonProperty("account_id")
#Column(name = "account_id")
#Id
private String accountId;
#Column(name = "account_status")
private int accountStatus;
#JsonProperty("timezone_id")
#Column(name = "timezone_id")
private int timezoneId;
private int timezoneOffsetUtc;
private String currency;
#Column(name = "timezone_name")
#JsonProperty("timezone_name")
private String timezoneName;
private String name;
#Column(name = "created_on",nullable = false, updatable = false)
#CreationTimestamp
private LocalDateTime createdOn;
#Column(name = "updated_on")
#UpdateTimestamp
private LocalDateTime updatedOn;
}
Repository:
#Repository()
public interface AccountRepository extends CrudRepository<Account, String> {
}
Tried with JpaRepository<Account, Long> too and flush after saving..but still getting id null in return list response of saveall()
Service:
#Service
public class AccountsService {
#Autowired
private AccountRepository repository;
#Override
#Transactional
public List<Account> saveAll(List<Account> accounts) {
//in case of JpaRepository
List<Account> savedAccounts= repository.saveAll(accounts);
repository.flush();
return savedAccounts;
//in case of CrudRepository
return (List<Account>)repository.saveAll(accounts);
}
}
when executing this
//accountsList received from FB API
List<Account> savedList=iAccountsService.saveAll(accountsList);
savedList.get(0).getId() **//this is coming as null**
Any sort of help is appreciated.
In your entity class :
Use this #GeneratedValue(strategy = GenerationType.IDENTITY)
public class Account implements Serializable{
#JsonIgnore
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false, insertable = false, updatable = false)
private Long id;
}
The problem is when I want to delete user I'm getting error in Spring Boot like that:
java.sql.SQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails (32506632_pam.badge, CONSTRAINT FK4aamfo6o0h5ejqjn40fv40jdw FOREIGN KEY (user_id) REFERENCES user (id))
I'm guessing that I need to delete data in cascade way. So I've placed CascadeType.REMOVE value to #OneToOne annotation like that, but it doesn't work:
badge entity
#Entity
#Data
#Table(name = "badge")
#AllArgsConstructor
#NoArgsConstructor
public class Badge {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonManagedReference
#ManyToMany(mappedBy = "badges", fetch = FetchType.LAZY)
private List<Reader> readers;
#OneToOne(cascade = CascadeType.REMOVE, orphanRemoval=true)
#JoinColumn(name = "user_id")
private User user;
private String number;
#Lob
#Basic(fetch = FetchType.LAZY)
private byte[] photo;
}
user entity
#Entity
#Data
#Table(name = "user")
#AllArgsConstructor
#NoArgsConstructor
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String lastname;
private String pesel;
private String email;
private String telephone;
private Integer age;
private String gender;
}
reader entity
#Entity
#Data
#Table(name = "reader")
#AllArgsConstructor
#NoArgsConstructor
public class Reader {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonBackReference
#ManyToMany(fetch = FetchType.LAZY)
private List<Badge> badges;
private String department;
private String room;
private Boolean status;
}
Class which loads initial data
#Component
public class DataLoader implements ApplicationRunner {
#Autowired
private UserService userService;
#Autowired
private BadgeService badgeService;
#Autowired
private ReaderService readerService;
#Override
public void run(ApplicationArguments args) throws Exception {
User user1 = new User(null, "Jan", "Kowal", "11111111111", "jan#kowal.pl", "+48111111111", new Integer(23), "male");
userService.saveUser(user1);
Reader reader1 = new Reader(null, null, "Warehouse", "207A", new Boolean("true"));
Badge badge1 = new Badge(null, Arrays.asList(reader1), user1, "738604289120", null);
badgeService.saveBadge(badge1);
reader1.setBadges(Arrays.asList(badge1));
readerService.saveReader(reader1);
}
}
Endpoint for deleting user - it uses repository which extends CrudRepository and uses default delete behavior.
#DeleteMapping("/deleteUserById/{id}")
private void deleteUserById(#PathVariable Long id) {
userService.deleteUserById(id);
}
Database structure in phpmyadmin
My goal is to delete user and associated badge with him, then to delete row in reader_badges table.
I have two tables
#Entity
#Table(name = "TAX_CATEGORY")
public class TaxCategory {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "CATEGORY", nullable = false)
private String category;
#Column(name = "TAX", nullable = false)
private Double tax;
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "PRICE", nullable = false)
private Double price;
#Column(name = "NAME", nullable = false)
private String name;
#OneToOne
#JoinColumn(name = "TAX_CATEGORY_ID")
private TaxCategory taxCategory;
Now I want to query
"Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id"
So List it would return is
ProductName ProductPrice Tax
but I am not able to get this data from two tables. Single table data is working fine.
public interface CustomRepositoryCustom {
public void customMethod();
}
public interface CustomRepository
extends JpaRepository<Account, Long>, CustomRepositoryCustom { }
public class CustomRepositoryImpl implements CustomRepositoryCustom {
public void customMethod() {
Query nativeQuery = entityManager.createNativeQuery("Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id");
return query.getResultList();
}
}
This throws exception that object is not managed bean. If I create custom object then also it gives similar type of issues.
Use the following JPA query to get the both tables data. Here used jpa query to fetch the product. From product object, can get the taxCategory.
public interface CustomRepository extends JpaRepository<Account, Long>, CustomRepositoryCustom {
Query("select product from Product as product join fetch product.taxCategory as taxCategory where taxCategory.id = :taxCategoryId")
public Product getProductByCategory(#Param Long taxCategoryId);
}
Instead of query method you can directly define JPA method to find products based on category Id as.
#Repository
#RepositoryRestResource
public interface ICountryRepository extends JpaRepository<Product , Long > {
List<Product> findByTaxCategory_Id(#Param Long Id);
}
I have an unidirectional OneToMany JPA entity mapping in my (Spring Framework + Spring Data + Hibernate JPA) project. Entity classes are like in the following code.(I have removed irrelevant class members for brevity).
#Entity
#Table(name = "employees")
class Employee{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "employee_id")
private List<DepartmentAssignment> departmentAssignments = new ArrayList<>();
}
#Entity
#Table(name = "department_assignments")
class DepartmentAssignment{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#NotNull
#Column(name = "employee_id")
private Integer employeeId;
#NotNull
#Column(name = "department_id")
private Integer departmentId;
}
#Entity
#Table(name = "departments")
class Department{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
}
And, in one of my service classes have a method to remove a DepartmentAssignment from an Employee like below.
public Employee deleteDepartmentAssignment(Integer empId, Integer deptAssignmentId) {
Employee employee = employeeRepository.findOne(empId);
if(employee != null) {
for ( DepartmentAssignment da : employee.getDepartmentAssignments()) {
if(da.getId().equals(deptAssignmentId)) {
employee.getDepartmentAssignments().remove(da);
employee = employeeRepository.save(employee);
break;
}
}
}
return employee;
}
However, calling above methods gives me an error: org.hibernate.exception.ConstraintViolationException ,and in the SQL log, I can see Column 'employee_id' cannot be null error for the last SQL statement of the transaction.
Can anybody tell me what I'm doing wrong here and how to get it fixed?
You don't need to add
#NotNull
#Column(name = "employee_id")
private Integer employeeId;
to the Employee, if you use #JoinColumn(name = "employee_id"). Try to remove it.
You can try the following, not sure why you use the plain id in the object. Thats not object relational mapping.
For more details see Hibernate triggering constraint violations using orphanRemoval
#Entity
#Table(name = "employees")
class Employee{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "employee", orphanRemoval = true)
private List<DepartmentAssignment> departmentAssignments = new ArrayList<>();
}
#Entity
#Table(name = "department_assignments")
class DepartmentAssignment{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(optional=false)
private Employee employee;
#ManyToOne(optional=false)
private Department department;
}
#Entity
#Table(name = "departments")
class Department{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
}
You must look .hbm.xml file and you should mapping your Entity in this file and
you can look this example
http://www.mkyong.com/hibernate/hibernate-one-to-many-relationship-example/
I hope it will be useful for you.
try removing
cascade = CascadeType.ALL
but im not 100% sure..