I am having an entity class which has two Entity class with one to many relationship. and there is one root parent entity which has ID variable which is being extended by all the entity. When i am trying to save MessagingEnablerCfg entity without initializing it's member variable(messagingProfiles and messagingSessions). it is getting saved. but if i will try to initialize the these set variable. it is throwing following error while doing save or update operation:
Caused by: java.sql.SQLException: Field 'messagingProfiles_id' doesn't have a default value
Note: if there is only one variable(either messagingProfiles or messagingSessions) in the MessagingEnablerCfg entity then it will work fine for saving or updating.
i feel it is weird but right now it is a big trouble for me.
Please find the below Entity class
#Entity
//#Indexed
#Proxy(lazy=false)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class MessagingConfigEntity{
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private String id;
//#Override
public String getId() {
return id;
}
}
another Entity class is:
#Entity
//#Indexed
#Proxy(lazy=false)
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown=true)
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class MessagingEnablerCfg extends MessagingConfigEntity {
private String serviceName;
#OneToMany(cascade = { CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.EAGER)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<MessagingProfileCfg> messagingProfiles = new HashSet<>();
//#OneToMany(cascade = { CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.EAGER)
#OneToMany(cascade = { CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.EAGER)
#JsonInclude(JsonInclude.Include.NON_EMPTY)
private Set<MessagingSessionCfg> messagingSessions = new HashSet<>();
public MessagingEnablerCfg(){
}
public MessagingEnablerCfg(String service){
this.serviceName = service;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public Set<MessagingSessionCfg> getMessagingSessions() {
return messagingSessions;
}
public void setMessagingSessions(Set<MessagingSessionCfg> messagingSessions)
{
this.messagingSessions = messagingSessions;
}
public Set<MessagingProfileCfg> getMessagingProfiles() {
return messagingProfiles;
}
public void setMessagingProfiles(Set<MessagingProfileCfg> messagingProfiles)
{
this.messagingProfiles = messagingProfiles;
}
public void addSession(MessagingSessionCfg session){
messagingSessions.add(session);
}
public void addProfile(MessagingProfileCfg profile){
messagingProfiles.add(profile);
}
}
Another Child class is:
#Entity
//#Indexed
#Proxy(lazy = false)
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class MessagingSessionCfg extends MessagingConfigEntity{
private String sessionName;
private String provider;
public MessagingSessionCfg(){
}
public MessagingSessionCfg(String name,String provider){
this.sessionName = name;
this.provider = provider;
}
public String getSessionName() {
return sessionName;
}
public void setSessionName(String sessionName) {
this.sessionName = sessionName;
}
public String getProvider() {
return provider;
}
public void setProvider(String provider) {
this.provider = provider;
}
public String toString(){
return "sessionName:"+sessionName+" provider:"+provider;
}
}
Ans last child entity is:
#Entity
//#Indexed
#Proxy(lazy=false)
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
public class MessagingProfileCfg extends MessagingConfigEntity {
private String profileId;
private String sourceAddress;
public MessagingProfileCfg(){
}
public MessagingProfileCfg(String profileId, String sourceAddress){
this.profileId = profileId;
this.sourceAddress = sourceAddress;
}
public String getProfileId() {
return profileId;
}
public void setProfileId(String profileId) {
this.profileId = profileId;
}
public String getSourceAddress() {
return sourceAddress;
}
public void setSourceAddress(String sourceAddress) {
this.sourceAddress = sourceAddress;
}
}
Related
I have two entities as below and the main problem is when I want to update the AccountRequestStatus entity. I save the integer enum code of AccountRequestStatusEnum in the database to persist the AcountRequest status in the whole application.
AccountRequestStatusEnum
public enum AccountRequestStatusEnum {
INITIAL(0),
SUCCESS(1);
private final Integer type;
AccountRequestStatusEnum(Integer type) {
this.type = type;
}
public Integer getType() {
return type;
}
public static AccountRequestStatusEnum of(Integer type) {
for (AccountRequestStatusEnum accountRequestStatusEnum : AccountRequestStatusEnum.values()) {
if (type.equals(accountRequestStatusEnum.getType()))
return accountRequestStatusEnum;
}
return null;
}
}
AccountRequest
#Entity
#Table(name = "T_ACCOUNT_REQUEST", uniqueConstraints = {#UniqueConstraint(columnNames = {"ACCOUNT_NO", "MESSAGE_ID"})})
#SequenceGenerator(
name = "SEQ_T_ACCOUNT_REQUEST",
sequenceName = "SEQ_T_ACCOUNT_REQUEST",
allocationSize = 1)
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(callSuper = false)
#ToString
public class AccountRequest extends AbstractAuditingEntity {
private Long id;
private String messageId;
private String issuer;
private EventType type;
private EventName name;
private String accountNo;
private LocalDateTime dateTime;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_T_ACCOUNT_REQUEST")
#Column(name = "ID", nullable = true, insertable = true, updatable = true, precision = 0)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "MESSAGE_ID")
public String getMessageId() {
return messageId;
}
public void setMessageId(String messageId) {
this.messageId = messageId;
}
#Column(name = "ISSUER")
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
#Transient
public EventType getType() {
return type;
}
public void setType(EventType type) {
this.type = type;
}
#Column(name = "TYPE")
public Integer getEventTypeCode() {
if (Objects.nonNull(type)) {
return type.getType();
} else return null;
}
public void setEventTypeCode(Integer typeCode) {
type = EventType.of(typeCode);
}
#Transient
public EventName getName() {
return name;
}
public void setName(EventName name) {
this.name = name;
}
#Column(name = "NAME")
public Integer getEventNameCode() {
if (Objects.nonNull(name)) {
return name.getType();
} else return null;
}
public void setEventNameCode(Integer type) {
name = EventName.of(type);
}
#Column(name = "ACCOUNT_NO")
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
#Column(name = "DATE_TIME")
public LocalDateTime getDateTime() {
return dateTime;
}
public void setDateTime(LocalDateTime dateTime) {
this.dateTime = dateTime;
}
}
AccountRequestStatus
#Entity
#Table(name = "T_ACCOUNT_REQUEST_STATUS")
#SequenceGenerator(
name = "SEQ_T_ACCOUNT_REQUEST_STATUS",
sequenceName = "SEQ_T_ACCOUNT_REQUEST_STATUS",
allocationSize = 1
)
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class AccountRequestStatus extends AbstractAuditingEntity {
private Long id;
private AccountRequestStatusEnum accountRequestStatusEnum;
private AccountRequest accountRequest;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_T_ACCOUNT_REQUEST_STATUS")
#Column(name = "ID", nullable = true, insertable = true, updatable = true, precision = 0)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Transient
public AccountRequestStatusEnum getAccountRequestStatusEnum() {
return accountRequestStatusEnum;
}
public void setAccountRequestStatusEnum(AccountRequestStatusEnum accountRequestStatusEnum) {
this.accountRequestStatusEnum = accountRequestStatusEnum;
}
#Column(name = "ACCOUNT_REQUEST_STATUS")
public Integer getAccountRequestStatusCode() {
if (Objects.nonNull(accountRequestStatusEnum)) {
return accountRequestStatusEnum.getType();
} else return null;
}
public void setAccountRequestStatusCode(Integer type) {
accountRequestStatusEnum = AccountRequestStatusEnum.of(type);
}
#ManyToOne(targetEntity = AccountRequest.class)
#JoinColumn(name = "ACCOUNT_REQUEST", referencedColumnName = "ID")
public AccountRequest getAccountRequest() {
return accountRequest;
}
public void setAccountRequest(AccountRequest accountRequest) {
this.accountRequest = accountRequest;
}
}
The first time that an account request comes from MQ my to application, I save the initial code of AccountRequestStatusEnum in service like below. This status persists properly and there is no problem, but when I want to update the AccountRequestStatus and add a new success code of AccountRequestStatusEnum (in another service) it won't be saved in DB.
This is the first service that is called after receiving the account request and saving the initial code.
#Service
#Transactional(readOnly = true)
public class AccountRequestServiceImpl implements IAccountRequestService {
#Value("${mq.event_argument_key}")
private String eventArgumentKey;
private final AccountRequestRepository accountRequestRepository;
private final AccountRequestStatusServiceImpl mqRequestStatusService;
private final EventToAccountRequestEntityMapper eventMapper;
private final AccountRequestMapper accountRequestMapper;
#Autowired
public AccountRequestServiceImpl(AccountRequestRepository accountRequestRepository,
AccountRequestStatusServiceImpl mqRequestStatusService,
EventToAccountRequestEntityMapper eventMapper,
AccountRequestMapper accountRequestMapper) {
this.accountRequestRepository = accountRequestRepository;
this.mqRequestStatusService = mqRequestStatusService;
this.eventMapper = eventMapper;
this.accountRequestMapper = accountRequestMapper;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)// to prevent rollback for whole receive method in mq service
public void saveAccountRequest(Event event) {
AccountRequest accountRequest = eventMapper.eventToAccountRequest(event, eventArgumentKey);
accountRequestRepository.save(accountRequest);
AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.INITIAL);
}
private void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
accountRequestStatusDto.setAccountRequestStatusEnum(status);
accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
mqRequestStatusService.saveAccountRequestStatus(accountRequestStatusDto);
}
}
This is the second service that should save the success code of AccountRequestStatus.
#Service
#Transactional(readOnly = true)
public class SyncLegacyAccountServiceImpl implements ISyncLegacyAccountService {
#Value("${mq.event_argument_key}")
private String eventArgumentKey;
#Value("${range.account_title_code}")
private String accountTitleCode;
private static final Logger log = LoggerFactory.getLogger(SyncLegacyAccountServiceImpl.class);
private final AccountMapRepository accountMapRepository;
private final AccountRequestRepository accountRequestRepository;
private final CustomerRepository customerRepository;
private final CustomerPersonRepository customerPersonRepository;
private final CustomerCompanyRepository customerCompanyRepository;
private final IMQService iMQService;
private final AccountRequestStatusServiceImpl accountRequestStatusServiceImpl;
private final GalaxyApi galaxyApi;
private final RangeApi rangeApi;
private final CustomerMapper customerMapper;
private final InquiryMapper inquiryMapper;
private final AccountRequestMapper accountRequestMapper;
private final EventToAccountRequestEntityMapper eventToAccountRequestMapper;
#Override
public void handleSyncRequest(Event event) {
saveSuccessfulAccountStatus(event); // ****** This is the main issue******
try {
CustomerAccountResponseDto galaxyData = getGalaxyData(event);
Optional<AccountMapEntity> optAccountMapEntity = accountMapRepository.findByNewAccountNo(event.getArgument().get(eventArgumentKey).toString());
if (!optAccountMapEntity.isPresent()) {
//openAccount(event);
} else {
AccountMapEntity accountMapEntity = optAccountMapEntity.get();
CustomerAccountResponseDto customerData = getCustomerData(accountMapEntity);
// save in legacy
}
} catch (Exception exception) {
handleEventRequestException(exception, event);
}
}
private void handleEventRequestException(Exception exception, Event event) {
if (exception instanceof RangeServiceException) {
log.error("Something went wrong with the Range service!");
throw new RangeServiceException();
} else if (exception instanceof GalaxySystemException) {
log.error("Something went wrong with the Galaxy service!");
NotifyAccountChangeResponse notifyAccountChangeResponse = MQUtil.buildAccountChangeResponse(new GalaxySystemException(), null, event.getMessageId());
iMQService.send(notifyAccountChangeResponse);
throw new GalaxySystemException();
}
}
public void saveSuccessfulAccountStatus(Event event) {
AccountRequest accountRequest = eventToAccountRequestMapper.eventToAccountRequest(event, eventArgumentKey);
AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.SUCCESS);
}
public void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
accountRequestStatusDto.setAccountRequestStatusEnum(status);
accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
accountRequestStatusServiceImpl.saveAccountRequestStatus(accountRequestStatusDto);
}
}
AccountRequestStatusServiceImpl
#Service
#Transactional(readOnly = true)
public class AccountRequestStatusServiceImpl implements IAccountRequestStatusService {
private final AccountRequestStatusRepository accountRequestStatusRepository;
private final AccountRequestStatusMapper accountRequestStatusMapper;
#Autowired
public AccountRequestStatusServiceImpl(AccountRequestStatusRepository accountRequestStatusRepository,
AccountRequestStatusMapper accountRequestStatusMapper) {
this.accountRequestStatusRepository = accountRequestStatusRepository;
this.accountRequestStatusMapper = accountRequestStatusMapper;
}
#Override
#Transactional
public void saveAccountRequestStatus(AccountRequestStatusDto accountRequestStatusDto) {
AccountRequestStatus accountRequestStatus = accountRequestStatusMapper.toAccountRequestStatus(accountRequestStatusDto);
accountRequestStatusRepository.save(accountRequestStatus);
}
}
AccountRequestDto
#Data
public class AccountRequestDto {
private Long id;
private String messageId;
private String issuer;
private EventType type;
private EventName name;
private String accountNo;
private LocalDateTime dateTime;
}
AccountRequestStatusDto
#Data
public class AccountRequestStatusDto {
private Long id;
private AccountRequestStatusEnum accountRequestStatusEnum;
private AccountRequestDto accountRequestDto;
}
AccountRequestStatusMapper
#Mapper(componentModel = "spring")
public interface AccountRequestStatusMapper extends EntityToDtoMapper<AccountRequestStatusDto, AccountRequestStatus>, DtoToEntityMapper<AccountRequestStatusDto, AccountRequestStatus> {
#Mapping(target = "accountRequest.id", source = "accountRequestDto.id")
#Mapping(target = "accountRequest.messageId", source = "accountRequestDto.messageId")
#Mapping(target = "accountRequest.issuer", source = "accountRequestDto.issuer")
#Mapping(target = "accountRequest.type", source = "accountRequestDto.type")
#Mapping(target = "accountRequest.name", source = "accountRequestDto.name")
#Mapping(target = "accountRequest.accountNo", source = "accountRequestDto.accountNo")
#Mapping(target = "accountRequest.dateTime", source = "accountRequestDto.dateTime")
#Named(value = "toAccountRequestStatus")
AccountRequestStatus toAccountRequestStatus(AccountRequestStatusDto accountRequestStatusDto);
#Mapping(target = "accountRequestDto.id", source = "accountRequest.id")
#Mapping(target = "accountRequestDto.messageId", source = "accountRequest.messageId")
#Mapping(target = "accountRequestDto.issuer", source = "accountRequest.issuer")
#Mapping(target = "accountRequestDto.type", source = "accountRequest.type")
#Mapping(target = "accountRequestDto.name", source = "accountRequest.name")
#Mapping(target = "accountRequestDto.accountNo", source = "accountRequest.accountNo")
#Mapping(target = "accountRequestDto.dateTime", source = "accountRequest.dateTime")
#Named(value = "toAccountRequestStatusDto")
AccountRequestStatusDto toAccountRequestStatusDto(AccountRequestStatus accountRequestStatus);
}
Your #Transactional annotations at class level are marked as readonly = true. This prevents any database persistence, hence no saving.
However, in your first service you have #Transactional(propagation = Propagation.REQUIRES_NEW), which creates/propagates a net new transaction outside the scope of the readonly transaction. Thus your first service is able to persist to the database and your second is not.
I would suggest removing the readonly = true or potentially adding the propagation = Propagation.REQUIRES_NEW to the second service's transaction.
I fixed the issue by adding the saveSuccessfulAccountRequest to the AccountRequestServiceImpl service as below, and calling the saveSuccessfulAccountRequest in SyncLegacyAccountServiceImpl service. The main point of this approach is that saveSuccessfulAccountRequest should have propagation = Propagation.REQUIRES_NEW, without this, it does not work!!! But actually, I am not sure why it should be propagation = Propagation.REQUIRES_NEW :)))
#Service
#Transactional(readOnly = true)
public class AccountRequestServiceImpl implements IAccountRequestService {
#Value("${mq.event_argument_key}")
private String eventArgumentKey;
private final AccountRequestRepository accountRequestRepository;
private final AccountRequestStatusServiceImpl mqRequestStatusService;
private final EventToAccountRequestEntityMapper eventMapper;
private final AccountRequestMapper accountRequestMapper;
#Autowired
public AccountRequestServiceImpl(AccountRequestRepository accountRequestRepository,
AccountRequestStatusServiceImpl mqRequestStatusService,
EventToAccountRequestEntityMapper eventMapper,
AccountRequestMapper accountRequestMapper) {
this.accountRequestRepository = accountRequestRepository;
this.mqRequestStatusService = mqRequestStatusService;
this.eventMapper = eventMapper;
this.accountRequestMapper = accountRequestMapper;
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)// to prevent rollback for whole receive method in mq service
public void saveAccountRequest(Event event) {
AccountRequest accountRequest = eventMapper.eventToAccountRequest(event, eventArgumentKey);
accountRequestRepository.save(accountRequest);
AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequest);
saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.INITIAL);
}
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveSuccessfulAccountRequest(Event event) {
AccountRequest accountRequestByMessageId = accountRequestRepository.findByMessageId(event.getMessageId());
AccountRequestDto accountRequestDto = accountRequestMapper.toDto(accountRequestByMessageId);
saveAccountRequestStatus(accountRequestDto, AccountRequestStatusEnum.SUCCESS);
}
private void saveAccountRequestStatus(AccountRequestDto accountRequestDto, AccountRequestStatusEnum status) {
AccountRequestStatusDto accountRequestStatusDto = new AccountRequestStatusDto();
accountRequestStatusDto.setAccountRequestStatusEnum(status);
accountRequestStatusDto.setAccountRequestDto(accountRequestDto);
mqRequestStatusService.saveAccountRequestStatus(accountRequestStatusDto);
}
}
#Service
#Transactional(readOnly = true)
public class SyncLegacyAccountServiceImpl implements ISyncLegacyAccountService {
#Value("${mq.event_argument_key}")
private String eventArgumentKey;
#Value("${range.account_title_code}")
private String accountTitleCode;
private static final Logger log = LoggerFactory.getLogger(SyncLegacyAccountServiceImpl.class);
private final CustomerRepository customerRepository;
private final CustomerPersonRepository customerPersonRepository;
private final CustomerCompanyRepository customerCompanyRepository;
private final AccountMapRepository accountMapRepository;
private final IMQService iMQService;
private final IAccountRequestService iAccountRequestService;
private final GalaxyApi galaxyApi;
private final RangeApi rangeApi;
private final CustomerMapper customerMapper;
private final InquiryMapper inquiryMapper;
public SyncLegacyAccountServiceImpl(CustomerRepository customerRepository,
CustomerPersonRepository customerPersonRepository,
CustomerCompanyRepository customerCompanyRepository,
AccountMapRepository accountMapRepository,
#Lazy IMQService iMQService,
IAccountRequestService iAccountRequestService,
GalaxyApi galaxyApi,
RangeApi rangeApi,
CustomerMapper customerMapper,
InquiryMapper inquiryMapper) {
this.customerRepository = customerRepository;
this.customerPersonRepository = customerPersonRepository;
this.customerCompanyRepository = customerCompanyRepository;
this.accountMapRepository = accountMapRepository;
this.iMQService = iMQService;
this.iAccountRequestService = iAccountRequestService;
this.galaxyApi = galaxyApi;
this.rangeApi = rangeApi;
this.customerMapper = customerMapper;
this.inquiryMapper = inquiryMapper;
}
#Override
public void handleSyncRequest(Event event) {
saveSuccessfulAccountRequestStatus(event);
try {
CustomerAccountResponseDto galaxyData = getGalaxyData(event);
Optional<AccountMapEntity> optAccountMapEntity = accountMapRepository.findByNewAccountNo(event.getArgument().get(eventArgumentKey).toString());
if (optAccountMapEntity.isPresent()) {
//openAccount(event);
}
} catch (Exception exception) {
handleEventRequestException(exception, event);
}
}
}
For a field in Process class as Map<String, List> messages, I have desinged the entities as below
#Entity(name = "Process")
#Table(name = "Process")
public class Process{
private String id;
private String processKey;
#OneToMany(targetEntity=Messages.class, cascade = CascadeType.ALL)
#JoinColumn(name = "processId")
#MapKey(name = "messageKey")
public Map<String, Messages> messagesMap = new HashMap<>();
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "xxx.hibernate.EntityIdGenerator")
#Override
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
public String getProcessKey() {
return processKey;
}
public void setProcessKey(String processKey) {
this.processKey = processKey;
}
public void setMessagesMap(Map<String, Messages> messagesMap) {
this.messagesMap = messagesMap;
}
public void putMessages(String key, String value) {
if(messagesMap.containsKey(key)) {
messagesMap.get(key).add(value);
} else {
Messages messages = new Messages();
messages.messageKey = key;
messages.messages = value;
messagesMap.put(key, messages);
}
}
// no getter for messages
}
And the Messges class
#Entity(name = "Messages")
#Table(name = "Messages")
public class Messages {
private String id;
#Column(name = "messageKey")
public String messageKey;
#Column(name = "message")
public String message;
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "xxx.hibernate.EntityIdGenerator")
#Override
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "messageKey", referencedColumnName = "messageKey")
public List<Messages> values = new ArrayList<>();
public void add(String messages) {
Messages messages = new Messages();
messages.messageKey = messageKey;
messages.messages = messages;
values.add(messages);
}
public String getMessageKey() {
return messageKey;
}
public void setMessageKey(String messageKey) {
this.messageKey = messageKey;
}
public String getMessages() {
return messages;
}
public void setMessages(String messages) {
this.messages = messages;
}
public void setValues(List<Messages> values) {
this.values = values;
}
}
In the service
Process process = new Process();
process.setProcessKey...
process.putMessages....
em.persist(process);
This results in a entry in Process table, but no inserts in Message table. No exception is thrown either. I have <property name="hibernate.show_sql" value="true"/> and I can see no Insert query is generated for Message table.
Do I need to persist Message object seperately ?
Is there any way to do it via persisting Process object only ?
Please also let me know if my mapping is incorrect.
Im working on a Spring Boot application and the repository method to retrieve by apiKey is returning null.
This is the controller:
#RestController
#RequestMapping("/api/wetlab")
public class WetLabController {
#Autowired
WetLabService wetLabService;
#GetMapping("/getAllWetlabsType")
#PreAuthorize("hasRole('INTERNAL')")
public List<WetLab> getAllWetlabs() {
return wetLabService.getAllWetLabs();
}
#GetMapping("/{apiKey}")
#PreAuthorize("hasRole('INTERNAL')")
public WetLab getByApiKey(#PathVariable UUID apiKey) {
return wetLabService.getByApiKey(apiKey);
}
#ExceptionHandler(DataRetrievalFailureException.class)
void handleNotFound(HttpServletResponse response, Exception e) throws IOException {
response.sendError(HttpStatus.NOT_FOUND.value(), e.getMessage());
}
}
This is the service
#Service
public class WetLabService {
#Autowired
WetLabRepository wetLabRepo;
public List<WetLab> getAllWetLabs() {
List<WetLab> wetLabs = new ArrayList<>();
wetLabRepo.findAll().forEach(wetLabs::add);
return wetLabs;
}
public WetLab getByApiKey(UUID apiKey) {
System.out.println(apiKey);
WetLab wetlabOpt = wetLabRepo.findByApiKey(apiKey);
return wetlabOpt;
// if (wetlabOpt.isPresent()) {
// } else {
// throw new NotFoundException("WetLab not found");
// }
}
}
And this is the repo
#Repository
public interface WetLabRepository extends CrudRepository<WetLab, Long> {
public WetLab findByApiKey(UUID apiKey);
}
And this is the wetlab class
#Entity
#Table(name = "wetlab")
public class WetLab {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "wetLab_seq")
#SequenceGenerator(name = "wetLab_seq", sequenceName = "wetLab_seq", allocationSize = 1)
private Long id;
#Column(name = "apiKey", updatable = true, nullable = false, unique = true, columnDefinition = "BINARY(16)")
#NotNull
private UUID apiKey;
#Column(name = "name", length = 50)
#NotNull
private String name;
#OneToMany
private List<Plot> plot;
// Accessors
public WetLab() {
}
public WetLab(Long id, UUID apiKey, String name) {
this.id = id;
this.apiKey = apiKey;
this.name = name;
}
}
The another method is working without errors and the path variable apiKey is not null.
Its strange because in other projects this approach worked fine...
Thanks you
Please help me in accessing Employee object in the below code using JAXB annotations. The application was developed in JPA SPRING. We are unable to access sub-object properties i.e, Employee properties
RESOURCE CORE FILE
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement(name="resource")
#Entity
#Table(name = "resource")
public class Resource implements java.io.Serializable {
private Integer resourceId;
private String resourceCode;
private String resourceName;
private String resourceNumber;
private Employee employee;
public Resource() {
}
public Resource(Employee employee,String resourceCode, String resourceName,
String resourceNumber
) {
this.employee = employee;
this.resourceCode = resourceCode;
this.resourceName = resourceName;
this.resourceNumber = resourceNumber;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "resource_id", unique = true, nullable = false)
public Integer getResourceId() {
return this.resourceId;
}
public void setResourceId(Integer resourceId) {
this.resourceId = resourceId;
}
#Column(name = "resource_code")
public String getResourceCode() {
return this.resourceCode;
}
public void setResourceCode(String resourceCode) {
this.resourceCode = resourceCode;
}
#Column(name = "resource_number")
public String getResourceNumber() {
return this.resourceNumber;
}
public void setResourceNumber(String resourceNumber) {
this.resourceNumber = resourceNumber;
}
#Column(name = "resource_name")
public String getResourceName() {
return this.resourceName;
}
public void setResourceName(String resourceName) {
this.resourceName = resourceName;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employee_id")
public Employee getEmployee() {
return this.employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
}
}
EMPLOYEE CORE FILE
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement(name="employee")
#Entity
#Table(name = "employee")
public class Employee implements java.io.Serializable {
private Integer employeeId;
private String employeeCode;
private String employeeName;
private List<Resource> resources = new ArrayList<Resource>(0);
public Employee() {
}
public Employee(String employeeCode, String employeeName,List<Resource> resources
) {
this.employeeCode = employeeCode;
this.employeeName = employeeName;
this.resources = resources;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "employee_id", unique = true, nullable = false)
public Integer getEmployeeId() {
return this.employeeId;
}
public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}
#Column(name = "employee_code")
public String getEmployeeCode() {
return this.employeeCode;
}
public void setEmployeeCode(String employeeCode) {
this.employeeCode = employeeCode;
}
#Column(name = "employee_name")
public String getEmployeeName() {
return this.employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employee")
public List<Resource> getResources() {
return this.resources;
}
public void setResources(List<Resource> resources) {
this.resources = resources;
}
}
You have to use the FetchType : Eager in RESOURCE CORE FILE of getEmployee() Method. Lazy fetch type is pulling only the parent object. Eager is pulling both.
I have problem with validation a very specific beans.
Let me give you some code first:
#Entity
#Table(name = "customers", schema = "public", uniqueConstraints = #UniqueConstraint(columnNames = {"cus_email" }))
public class Customers extends ModelObject implements java.io.Serializable {
private static final long serialVersionUID = -3197505684643025341L;
private long cusId;
private String cusEmail;
private String cusPassword;
private Addresses shippingAddress;
private Addresses invoiceAddress;
#Id
#Column(name = "cus_id", unique = true, nullable = false)
#SequenceGenerator(name = "cus_seq", sequenceName = "customers_cus_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "cus_seq")
#NotNull
public long getCusId() {
return cusId;
}
public void setCusId(long cusId) {
this.cusId = cusId;
}
#NotEmpty
#Size(min=5, max=255)
#Email
#Column(name = "cus_email", unique = true, nullable = false, length = 255)
public String getCusEmail() {
return cusEmail;
}
public void setCusEmail(String cusEmail) {
this.cusEmail = cusEmail;
}
#NotNull
#Column(name = "cus_password", nullable = false)
public String getCusPassword() {
return cusPassword;
}
public void setCusPassword(String cusPassword) {
this.cusPassword = cusPassword;
}
#NotNull
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cus_shipping_adr_id", nullable = false)
#Cascade(value = CascadeType.ALL)
#Valid
public Addresses getShippingAddress() {
return shippingAddress;
}
public void setShippingAddress(Addresses cusShippingAddress) {
this.shippingAddress = cusShippingAddress;
}
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cus_invoice_adr_id", nullable = true)
#Cascade(value = CascadeType.ALL)
#Valid
public Addresses getInvoiceAddress() {
return invoiceAddress;
}
public void setInvoiceAddress(Addresses cusInvoiceAddress) {
this.invoiceAddress = cusInvoiceAddress;
}
}
As you can see, I have here two address fields - one for shipping address, the other for invoice address.
The validation for each type of address should be different, as e.g. I don't need VAT number in shipping address, but I may want that in invoice.
I used groups to perform different validation on invoice address and shipping address which works OK if I do manual validation of address field.
But now I'd like to validate whole Customer object with addresses (if available).
I tried to do that with code below:
private void validateCustomerData() throws CustomerValidationException {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Customers>> constraintViolations;
constraintViolations = validator.validate(customer, Default.class, InvoiceAddressCheck.class, ShippingAddressCheck.class);
if (!constraintViolations.isEmpty()) {
throw new CustomerValidationException(3, Message.CustomerDataException, constraintViolations);
}
}
Of course this doesn't work as it supposed, since both validations are run on both instances of address objects inside customer object, so I get errors in shipping address from InvoiceAddressCheck interface and errors in invoice address from ShippingAddressCheck.
Here is shortened declaration of Addresses bean:
#Entity
#Table(name = "addresses", schema = "public")
#TypeDef(name = "genderConverter", typeClass = GenderConverter.class)
public class Addresses extends ModelObject implements Serializable{
private static final long serialVersionUID = -1123044739678014182L;
private long adrId;
private String street;
private String houseNo;
private String zipCode;
private String state;
private String countryCode;
private String vatNo;
private Customers customersShipping;
private Customers customersInvoice;
public Addresses() {}
public Addresses(long adrId) {
super();
this.adrId = adrId;
}
#Id
#Column(name = "adr_id", unique = true, nullable = false)
#SequenceGenerator(name = "adr_seq", sequenceName = "adr_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "adr_seq")
#NotNull
public long getAdrId() {
return adrId;
}
public void setAdrId(long adrId) {
this.adrId = adrId;
}
#NotNull
#Column(name = "adr_street", nullable = false)
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
#NotEmpty(groups = ShippingAddressCheck.class)
#Column(name = "adr_house_no")
public String getHouseNo() {
return houseNo;
}
#NotEmpty(groups = ShippingAddressCheck.class)
#Column(name = "adr_zip_code")
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
#Column(name = "adr_vat_no")
#NotEmpty(groups = InvoiceAddressCheck.class)
public String getVatNo() {
return vatNo;
}
public void setVatNo(String vatNo) {
this.vatNo = vatNo;
}
#OneToOne(fetch = FetchType.LAZY, mappedBy = "shippingAddress")
public Customers getCustomersShipping() {
return customersShipping;
}
public void setCustomersShipping(Customers customersShipping) {
this.customersShipping = customersShipping;
}
#OneToOne(fetch = FetchType.LAZY, mappedBy = "invoiceAddress")
public Customers getCustomersInvoice() {
return customersInvoice;
}
public void setCustomersInvoice(Customers customersInvoice) {
this.customersInvoice = customersInvoice;
}
}
Is there any way to run the validation, so that invoiceAddress is validated with InvoiceAddressCheck group and shippingAddress validated with ShippingAddressCheck group, but run during validation of Customer object?
I know that I can do it manually for each subobject, but that is not the point in here.
Temp solution for now is to write custom validation for invoice field, so it checks only InvoiceAddressCheck.
Here is the code I have
Annotation:
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Constraint(validatedBy = {InvoiceAddressValidator.class })
public #interface InvoiceAddressChecker {
String message() default "Invoice address incorrect.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validator:
public class InvoiceAddressValidator implements ConstraintValidator<InvoiceAddressChecker, Addresses> {
#Override
public void initialize(InvoiceAddressChecker params) {
}
#Override
public boolean isValid(Addresses invoiceAddress, ConstraintValidatorContext context) {
// invoice address is optional
if (invoiceAddress == null) {
return true;
}
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Addresses>> constraintViolations;
constraintViolations = validator.validate(invoiceAddress, Default.class, InvoiceAddressCheck.class);
if (constraintViolations.isEmpty()) {
return true;
} else {
context.disableDefaultConstraintViolation();
Iterator<ConstraintViolation<Addresses>> iter = constraintViolations.iterator();
while (iter.hasNext()) {
ConstraintViolation<Addresses> violation = iter.next();
context.buildConstraintViolationWithTemplate(violation.getMessage()).addNode(
violation.getPropertyPath().toString()).addConstraintViolation();
}
return false;
}
}
}
And model annotation:
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cus_invoice_adr_id", nullable = true)
#Cascade(value = CascadeType.ALL)
#InvoiceAddressChecker
public Addresses getInvoiceAddress() {
return invoiceAddress;
}
It's not really great solution, but it does what I need.
If you figure out better solution, please let me know :)