Issue
I am using Spring Boot 2.7.2 , Java/JDK 18 , PostgreSQL 14. DDL SQL script
DROP TABLE IF EXISTS tenant;
CREATE TABLE tenant
(
id smallint primary key,
company_tax_code character varying(14),
period character varying(16), -- 2021070420220705
created timestamp with time zone
);
CREATE OR REPLACE FUNCTION set_id_tenant()
RETURNS trigger AS
$$
DECLARE
BEGIN
new.id = (select coalesce(max(id), -32769) from tenant) + 1;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER trigger_insert_without_id_tenant
BEFORE INSERT
ON tenant
FOR EACH ROW
EXECUTE PROCEDURE set_id_tenant();
CREATE INDEX tenant_idx ON tenant (id);
COMMIT;
--------------------------------------------------------------------------------
-- 1.
DROP TABLE IF EXISTS account;
CREATE TABLE account
(
id smallint,
account_number character varying(32) not null unique,
account_name character varying(128) not null,
account_name_english character varying(128),
account_name_chinese character varying(128),
account_name_korean character varying(128),
description character varying(512),
parent_id smallint,
internal_code_id character varying(128),
grade smallint,
is_parent boolean not null,
account_category_kind smallint not null,
is_postable_in_foreign_currency boolean not null,
detail_by_account_object boolean not null,
account_object_type smallint,
detail_by_bank_account boolean not null,
detail_by_job boolean not null,
detail_by_job_kind smallint,
detail_by_project_work boolean not null,
detail_by_project_work_kind smallint,
detail_by_order boolean not null,
detail_by_order_kind smallint,
detail_by_contract boolean not null,
detail_by_contract_kind smallint,
detail_by_expense_item boolean not null,
detail_by_expense_item_kind smallint,
detail_by_department boolean not null,
detail_by_department_kind smallint,
detail_by_list_item boolean not null,
detail_by_list_item_kind smallint,
active_status boolean not null,
created timestamp with time zone,
created_by character varying(64),
modified timestamp with time zone,
modified_by character varying(64),
sort_internal_code_id character varying(128),
detail_by_pu_contract boolean not null,
detail_by_pu_contract_kind smallint,
tenant_id smallint,
PRIMARY KEY (id, tenant_id),
CONSTRAINT fk_tenant FOREIGN KEY (tenant_id) REFERENCES tenant (id)
);
CREATE OR REPLACE FUNCTION account_setId()
RETURNS trigger AS
$$
DECLARE
BEGIN
new.id = (select coalesce(max(id), -32769) from account where tenant_id = new.tenant_id) + 1;
RETURN NEW;
END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER account_trig_insertWithoutId
BEFORE INSERT
ON account
FOR EACH ROW
EXECUTE PROCEDURE account_setId();
CREATE INDEX account_idx ON account (id, tenant_id);
COMMENT ON TABLE public.account IS 'Danh mục tài khoản';
COMMENT ON COLUMN public.account.id IS 'PK Tài khoản';
COMMENT ON COLUMN public.account.account_number IS 'Số hiệu tài khoản';
COMMENT ON COLUMN public.account.account_name IS 'Tên tài khoản';
COMMENT ON COLUMN public.account.account_name_english IS 'Tên tài khoản bằng Tiếng Anh';
COMMENT ON COLUMN public.account.account_name_chinese IS 'Tên tài khoản bằng Tiếng Trung';
COMMENT ON COLUMN public.account.account_name_korean IS 'Tên tài khoản bằng Tiếng Hàn Quốc';
COMMENT ON COLUMN public.account.description IS 'Diễn giải';
COMMENT ON COLUMN public.account.parent_id IS 'TK Tổng hợp';
COMMENT ON COLUMN public.account.grade IS 'Cấp bậc';
COMMENT ON COLUMN public.account.is_parent IS 'Là TK tổng hợp';
COMMENT ON COLUMN public.account.account_category_kind IS 'Tính chất tài khoản: 0: Dư nợ; 1: Dư có; 2: Lưỡng tính';
COMMENT ON COLUMN public.account.is_postable_in_foreign_currency IS 'Có hạch toán ngoại tệ';
COMMENT ON COLUMN public.account.detail_by_account_object IS 'Chi tiết theo đối tượng';
COMMENT ON COLUMN public.account.account_object_type IS 'Loại đối tượng: 0 - Nhà cung cấp, 1- Khách hàng, 2- Nhân viên';
COMMENT ON COLUMN public.account.detail_by_bank_account IS 'Chi tiết theo tài khoản ngân hàng';
COMMENT ON COLUMN public.account.detail_by_job IS 'Chi tiết theo đối tượng tập hợp chi phí';
COMMENT ON COLUMN public.account.detail_by_job_kind IS '0 = Chỉ cảnh báo; 1 = Bắt buộc nhập';
COMMENT ON COLUMN public.account.detail_by_project_work IS 'Chi tiết theo công trình, vụ việc';
COMMENT ON COLUMN public.account.detail_by_project_work_kind IS '0 = Chỉ cảnh báo; 1 = Bắt buộc nhập';
COMMENT ON COLUMN public.account.detail_by_order IS 'Chi tiết theo đơn hàng';
COMMENT ON COLUMN public.account.detail_by_order_kind IS '0 = Chỉ cảnh báo; 1 = Bắt buộc nhập';
COMMENT ON COLUMN public.account.detail_by_contract IS 'Chi tiết theo hợp đồng';
COMMENT ON COLUMN public.account.detail_by_contract_kind IS '0 = Chỉ cảnh báo; 1 = Bắt buộc nhập';
COMMENT ON COLUMN public.account.detail_by_expense_item IS 'Chi tiết theo Khoản mục CP';
COMMENT ON COLUMN public.account.detail_by_expense_item_kind IS '0 = Chỉ cảnh báo; 1 = Bắt buộc nhập';
COMMENT ON COLUMN public.account.detail_by_department IS 'Chi tiết theo đơn vị';
COMMENT ON COLUMN public.account.detail_by_department_kind IS '0 = Chỉ cảnh báo; 1 = Bắt buộc nhập';
COMMENT ON COLUMN public.account.detail_by_list_item IS 'Chi tiết theo mã thống kê';
COMMENT ON COLUMN public.account.detail_by_list_item_kind IS '0 = Chỉ cảnh báo; 1 = Bắt buộc nhập';
COMMENT ON COLUMN public.account.active_status IS 'Trạng thái theo dõi';
COMMENT ON COLUMN public.account.sort_internal_code_id IS 'Cột dùng để sort trên báo cáo. Không sử dụng trên giao diện.';
COMMIT;
Entity
package com.example.BLModel;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.MapsId;
import javax.persistence.Table;
import java.time.OffsetDateTime;
@Entity
@Table(name = "account")
public class Account {
@EmbeddedId
private AccountId id;
@MapsId("tenantId")
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "tenant_id", nullable = false)
private Tenant tenant;
@Column(name = "account_number", nullable = false, length = 32)
private String accountNumber;
@Column(name = "account_name", nullable = false, length = 128)
private String accountName;
@Column(name = "account_name_english", length = 128)
private String accountNameEnglish;
@Column(name = "account_name_chinese", length = 128)
private String accountNameChinese;
@Column(name = "account_name_korean", length = 128)
private String accountNameKorean;
@Column(name = "description", length = 512)
private String description;
@Column(name = "parent_id")
private Integer parentId;
@Column(name = "bkit_code_id", length = 128)
private String bkitCodeId;
@Column(name = "grade")
private Integer grade;
@Column(name = "is_parent", nullable = false)
private Boolean isParent = false;
@Column(name = "account_category_kind", nullable = false)
private Integer accountCategoryKind;
@Column(name = "is_postable_in_foreign_currency", nullable = false)
private Boolean isPostableInForeignCurrency = false;
@Column(name = "detail_by_account_object", nullable = false)
private Boolean detailByAccountObject = false;
@Column(name = "account_object_type")
private Integer accountObjectType;
@Column(name = "detail_by_bank_account", nullable = false)
private Boolean detailByBankAccount = false;
@Column(name = "detail_by_job", nullable = false)
private Boolean detailByJob = false;
@Column(name = "detail_by_job_kind")
private Integer detailByJobKind;
@Column(name = "detail_by_project_work", nullable = false)
private Boolean detailByProjectWork = false;
@Column(name = "detail_by_project_work_kind")
private Integer detailByProjectWorkKind;
@Column(name = "detail_by_order", nullable = false)
private Boolean detailByOrder = false;
@Column(name = "detail_by_order_kind")
private Integer detailByOrderKind;
@Column(name = "detail_by_contract", nullable = false)
private Boolean detailByContract = false;
@Column(name = "detail_by_contract_kind")
private Integer detailByContractKind;
@Column(name = "detail_by_expense_item", nullable = false)
private Boolean detailByExpenseItem = false;
@Column(name = "detail_by_expense_item_kind")
private Integer detailByExpenseItemKind;
@Column(name = "detail_by_department", nullable = false)
private Boolean detailByDepartment = false;
@Column(name = "detail_by_department_kind")
private Integer detailByDepartmentKind;
@Column(name = "detail_by_list_item", nullable = false)
private Boolean detailByListItem = false;
@Column(name = "detail_by_list_item_kind")
private Integer detailByListItemKind;
@Column(name = "active_status", nullable = false)
private Boolean activeStatus = false;
@Column(name = "created")
private OffsetDateTime created;
@Column(name = "created_by", length = 64)
private String createdBy;
@Column(name = "modified")
private OffsetDateTime modified;
@Column(name = "modified_by", length = 64)
private String modifiedBy;
@Column(name = "sort_bkit_code_id", length = 128)
private String sortBkitCodeId;
@Column(name = "detail_by_pu_contract", nullable = false)
private Boolean detailByPuContract = false;
@Column(name = "detail_by_pu_contract_kind")
private Integer detailByPuContractKind;
public AccountId getId() {
return id;
}
public void setId(AccountId id) {
this.id = id;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
public String getAccountNumber() {
return accountNumber;
}
public void setAccountNumber(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getAccountNameEnglish() {
return accountNameEnglish;
}
public void setAccountNameEnglish(String accountNameEnglish) {
this.accountNameEnglish = accountNameEnglish;
}
public String getAccountNameChinese() {
return accountNameChinese;
}
public void setAccountNameChinese(String accountNameChinese) {
this.accountNameChinese = accountNameChinese;
}
public String getAccountNameKorean() {
return accountNameKorean;
}
public void setAccountNameKorean(String accountNameKorean) {
this.accountNameKorean = accountNameKorean;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getParentId() {
return parentId;
}
public void setParentId(Integer parentId) {
this.parentId = parentId;
}
public String getBkitCodeId() {
return bkitCodeId;
}
public void setBkitCodeId(String bkitCodeId) {
this.bkitCodeId = bkitCodeId;
}
public Integer getGrade() {
return grade;
}
public void setGrade(Integer grade) {
this.grade = grade;
}
public Boolean getIsParent() {
return isParent;
}
public void setIsParent(Boolean isParent) {
this.isParent = isParent;
}
public Integer getAccountCategoryKind() {
return accountCategoryKind;
}
public void setAccountCategoryKind(Integer accountCategoryKind) {
this.accountCategoryKind = accountCategoryKind;
}
public Boolean getIsPostableInForeignCurrency() {
return isPostableInForeignCurrency;
}
public void setIsPostableInForeignCurrency(Boolean isPostableInForeignCurrency) {
this.isPostableInForeignCurrency = isPostableInForeignCurrency;
}
public Boolean getDetailByAccountObject() {
return detailByAccountObject;
}
public void setDetailByAccountObject(Boolean detailByAccountObject) {
this.detailByAccountObject = detailByAccountObject;
}
public Integer getAccountObjectType() {
return accountObjectType;
}
public void setAccountObjectType(Integer accountObjectType) {
this.accountObjectType = accountObjectType;
}
public Boolean getDetailByBankAccount() {
return detailByBankAccount;
}
public void setDetailByBankAccount(Boolean detailByBankAccount) {
this.detailByBankAccount = detailByBankAccount;
}
public Boolean getDetailByJob() {
return detailByJob;
}
public void setDetailByJob(Boolean detailByJob) {
this.detailByJob = detailByJob;
}
public Integer getDetailByJobKind() {
return detailByJobKind;
}
public void setDetailByJobKind(Integer detailByJobKind) {
this.detailByJobKind = detailByJobKind;
}
public Boolean getDetailByProjectWork() {
return detailByProjectWork;
}
public void setDetailByProjectWork(Boolean detailByProjectWork) {
this.detailByProjectWork = detailByProjectWork;
}
public Integer getDetailByProjectWorkKind() {
return detailByProjectWorkKind;
}
public void setDetailByProjectWorkKind(Integer detailByProjectWorkKind) {
this.detailByProjectWorkKind = detailByProjectWorkKind;
}
public Boolean getDetailByOrder() {
return detailByOrder;
}
public void setDetailByOrder(Boolean detailByOrder) {
this.detailByOrder = detailByOrder;
}
public Integer getDetailByOrderKind() {
return detailByOrderKind;
}
public void setDetailByOrderKind(Integer detailByOrderKind) {
this.detailByOrderKind = detailByOrderKind;
}
public Boolean getDetailByContract() {
return detailByContract;
}
public void setDetailByContract(Boolean detailByContract) {
this.detailByContract = detailByContract;
}
public Integer getDetailByContractKind() {
return detailByContractKind;
}
public void setDetailByContractKind(Integer detailByContractKind) {
this.detailByContractKind = detailByContractKind;
}
public Boolean getDetailByExpenseItem() {
return detailByExpenseItem;
}
public void setDetailByExpenseItem(Boolean detailByExpenseItem) {
this.detailByExpenseItem = detailByExpenseItem;
}
public Integer getDetailByExpenseItemKind() {
return detailByExpenseItemKind;
}
public void setDetailByExpenseItemKind(Integer detailByExpenseItemKind) {
this.detailByExpenseItemKind = detailByExpenseItemKind;
}
public Boolean getDetailByDepartment() {
return detailByDepartment;
}
public void setDetailByDepartment(Boolean detailByDepartment) {
this.detailByDepartment = detailByDepartment;
}
public Integer getDetailByDepartmentKind() {
return detailByDepartmentKind;
}
public void setDetailByDepartmentKind(Integer detailByDepartmentKind) {
this.detailByDepartmentKind = detailByDepartmentKind;
}
public Boolean getDetailByListItem() {
return detailByListItem;
}
public void setDetailByListItem(Boolean detailByListItem) {
this.detailByListItem = detailByListItem;
}
public Integer getDetailByListItemKind() {
return detailByListItemKind;
}
public void setDetailByListItemKind(Integer detailByListItemKind) {
this.detailByListItemKind = detailByListItemKind;
}
public Boolean getActiveStatus() {
return activeStatus;
}
public void setActiveStatus(Boolean activeStatus) {
this.activeStatus = activeStatus;
}
public OffsetDateTime getCreated() {
return created;
}
public void setCreated(OffsetDateTime created) {
this.created = created;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public OffsetDateTime getModified() {
return modified;
}
public void setModified(OffsetDateTime modified) {
this.modified = modified;
}
public String getModifiedBy() {
return modifiedBy;
}
public void setModifiedBy(String modifiedBy) {
this.modifiedBy = modifiedBy;
}
public String getSortBkitCodeId() {
return sortBkitCodeId;
}
public void setSortBkitCodeId(String sortBkitCodeId) {
this.sortBkitCodeId = sortBkitCodeId;
}
public Boolean getDetailByPuContract() {
return detailByPuContract;
}
public void setDetailByPuContract(Boolean detailByPuContract) {
this.detailByPuContract = detailByPuContract;
}
public Integer getDetailByPuContractKind() {
return detailByPuContractKind;
}
public void setDetailByPuContractKind(Integer detailByPuContractKind) {
this.detailByPuContractKind = detailByPuContractKind;
}
}
package com.example.BLModel;
import org.hibernate.Hibernate;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Entity;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
public class AccountId implements Serializable {
private static final long serialVersionUID = 2728412978200770912L;
@Column(name = "id", nullable = false)
private Integer id;
@Column(name = "tenant_id", nullable = false)
private Integer tenantId;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getTenantId() {
return tenantId;
}
public void setTenantId(Integer tenantId) {
this.tenantId = tenantId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
AccountId entity = (AccountId) o;
return Objects.equals(this.tenantId, entity.tenantId) &&
Objects.equals(this.id, entity.id);
}
@Override
public int hashCode() {
return Objects.hash(tenantId, id);
}
}
My services
package com.example.service;
import com.example.BLModel.*;
import org.springframework.stereotype.Service;
import java.util.*;
public interface AccountService {
// FIXME: Need get Account belong to tenant_id .
List<Account> getAll();
Optional<Account> getAccount(AccountId accountId);
Account addAccount(Account account);
void deleteAccount(AccountId accountId);
}
and
package com.example.service;
import com.example.BLModel.Account;
import com.example.BLModel.AccountId;
import com.example.repository.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
AccountRepository accountRepository;
// FIXME: Need get Account belong to tenant_id .
@Override
public List<Account> getAll() {
return accountRepository.findAll(); // <-- Need revise.
}
@Override
public Optional<Account> getAccount(AccountId accountId) {
return Optional.empty();
}
@Override
public Account addAccount(Account account) {
return null;
}
@Override
public void deleteAccount(AccountId accountId) {
}
}
Controller
package com.example.controller;
import com.example.BLModel.Account;
import com.example.BLModel.AccountId;
import com.example.service.AccountService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.Optional;
@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
private static final Logger LOGGER = LoggerFactory.getLogger(AccountDefaultController.class);
@Autowired
AccountService accountService;
// @RequestAuthorization
//FIXME: Need revise.
/**
* Get all accounts belong to a specific tenant.<br/>
* GET http://localhost:80/account/all .
*
* @return
*/
@RequestMapping(value = "/all", method = RequestMethod.GET)
public ResponseEntity<List<Account>> getAllAccounts() {
LOGGER.info("Call method getAllAccounts() .");
return new ResponseEntity<>(accountService.getAll(), HttpStatus.OK);
}
/**
* Get a specific account by its composite primary key.<br/>
* GET http://localhost:80/account/1/42 .
*
* @param tenant_id
* @param id
* @return
*/
@GetMapping(value = "/{id}")
public ResponseEntity<Account> viewDetailAccount(@PathVariable("tenant_id") Integer tenant_id, @PathVariable("id") Integer id) {
LOGGER.info("Call method viewDetailAccount() .");
AccountId accountId = new AccountId();
accountId.setId(id);
accountId.setTenantId(tenant_id);
Optional<Account> accountOptional = accountService.getAccount(accountId);
Account result = null;
if (accountOptional.isPresent()) {
result = accountOptional.get();
}
return new ResponseEntity<>(result, HttpStatus.OK);
}
/**
* Get inv_type by composite primary key.<br/>
* POST http://localhost:80/inv_type
*
* @param input
* @return
*/
@PostMapping()
public ResponseEntity<Account> addAccount(@RequestBody Account input) {
LOGGER.info("Call method addAccount() .");
Account account = new Account();
account.setAccountName(input.getAccountName());
account.setCreated(OffsetDateTime.now());
account.setAccountCategoryKind(input.getAccountCategoryKind());
account.setActiveStatus(input.getActiveStatus());
account.setAccountNameKorean(input.getAccountNameKorean());
accountService.addAccount(account);
return new ResponseEntity<>(account, HttpStatus.OK);
}
/**
* Edit account by its composite primary key.<br/>
* PATCH http://localhost:80/account/1/42 .
*
* @param tenant_id
* @param id
* @param input
* @return
*/
@PatchMapping(value = "/{tenant_id}/{id}")
public ResponseEntity<Account> editAccount(@PathVariable("tenant_id") Integer tenant_id, @PathVariable("id") Integer id, @RequestBody Account input) {
LOGGER.info("Call method editAccount() .");
AccountId accountId = new AccountId();
accountId.setTenantId(tenant_id);
accountId.setId(id);
Account account = (accountService.getAccount(accountId)).get();
account.setAccountNumber(input.getAccountNumber());
account.setModified(OffsetDateTime.now());
account.setAccountNameKorean(input.getAccountNameKorean());
account.setActiveStatus(input.getActiveStatus());
account.setAccountNameChinese(input.getAccountNameChinese());
accountService.addAccount(account);
return new ResponseEntity<>(account, HttpStatus.OK);
}
/**
* Delete an account by its composite primary key.<br/>
* DELETE http://localhost:80/account/1/42
*
* @param tenant_id
* @param id
* @return
*/
@DeleteMapping(value = "/{tenant_id}/{id}")
public ResponseEntity<Object> deleteAccount(@PathVariable("tenant_id") Integer tenant_id, @PathVariable("id") Integer id) {
AccountId accountId = new AccountId();
accountId.setId(id);
accountId.setTenantId(tenant_id);
accountService.deleteAccount(accountId);
return new ResponseEntity<>(null, HttpStatus.OK);
}
}
How to find all accounts by a tenant_id
? I can use native SQL query, but I want use Spring Data JPA API more than.
Solution
You can use Spring Data Repository query keywords on your composite key by find all accounts by following code.
@Repository
public interface AccountRepository extends JpaRepository<Account, AccountId> {
List<Account> findAllByTenantId(Integer tenant_id);
}
Answered By - Jonathan JOhx
Answer Checked By - Cary Denson (JavaFixing Admin)