JPA find by Api Key returns null - java

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

Related

Problem in updating the entity with unidirectional #ManyToOne relation

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);
}
}
}

Multiple #Embedded fields of same type are always null after persisting

I've implemented the following hierachy:
Abstract Superclass: ConnectionTechnologyDetails
Subclass: AS2
Embeddable: AS2Details
The Subclass AS2 has additional attributes and methods in comparsion to it's superclass. The additional attributes are #Embedded types of AS2Details.
When I'm saving the AS2 subclass instance to it's repository, the #Embedded fields seem to get lost. When I'm priniting the saved instance all #Embedded fields are null. I don't know why because printing before saving to repository results in filled #Embedded fields.
Outcome after persist
"connectionTechnologyDetails": {
"id": 2,
"connectionTechnologyName": "AS2",
"senderReceiverIds": {
"senderIDOutbound": "HLCU",
"receiverIDOutbound": "BASF",
"senderIdInbound": "BASF",
"receiverIdInbound": "HLCU"
},
"companyTestDetails": null,
"companyProdDetails": null,
"hlagTestDetails": null,
"hlagProdDetails": null,
"testString": "testStringShowsUp"
}
ConnectionTechnologyDetails
import javax.persistence.*;
import javax.validation.constraints.NotNull;
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING, name = "TECH_TYPE")
public abstract class ConnectionTechnologyDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne
#PrimaryKeyJoinColumn
private Connection connection;
#NotNull
private ConnectionTechnologyName connectionTechnologyName;
#Embedded
private SenderReceiverIds senderReceiverIds;
public ConnectionTechnologyDetails(ConnectionTechnologyName connectionTechnologyName, SenderReceiverIds senderReceiverIds) {
this.connectionTechnologyName = connectionTechnologyName;
this.senderReceiverIds = senderReceiverIds;
}
public ConnectionTechnologyDetails() {
}
public ConnectionTechnologyName getConnectionTechnologyName() {
return connectionTechnologyName;
}
public void setConnectionTechnologyName(ConnectionTechnologyName connectionTechnologyName) {
this.connectionTechnologyName = connectionTechnologyName;
}
public SenderReceiverIds getSenderReceiverIds() {
return senderReceiverIds;
}
public void setSenderReceiverIds(SenderReceiverIds senderReceiverIds) {
this.senderReceiverIds = senderReceiverIds;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public enum ConnectionTechnologyName {
SMTP,
AS2,
UNSECURE_FTP,
SECURE_FTP,
FTP_SECURE
}
#Override
public String toString() {
return "ConnectionTechnologyDetails: Name=" +getConnectionTechnologyName().name()
+ " SenderReceiverIds=" + getSenderReceiverIds();
}
}
AS2
(Even tried to hardcode the first #Embedded field. But even this doesn't work.)
import javax.persistence.*;
import javax.validation.constraints.NotNull;
#Entity(name = "AS2")
#Table(name = "AS2")
#DiscriminatorValue("AS2")
public class AS2 extends ConnectionTechnologyDetails {
#NotNull
#Embedded
private AS2Details companyTestDetails = new AS2Details("192.1.168.1",
"ctest", "localhost:8080", "ctest2", "90", "111.00.5.4",
"scharpe", "+9+992++*9*+", "AS3");
#NotNull
#Embedded
private AS2Details companyProdDetails;
#NotNull
#Embedded
private AS2Details hlagTestDetails;
#NotNull
#Embedded
private AS2Details hlagProdDetails;
private String testString;
public AS2(ConnectionTechnologyName connectionTechnologyName, SenderReceiverIds senderReceiverIds,
AS2Details companyTestDetails, AS2Details companyProdDetails, AS2Details hlagTestDetails,
AS2Details hlagProdDetails) {
super(connectionTechnologyName, senderReceiverIds);
System.err.println("ERROR---- companyTestDetails = " + companyTestDetails);
this.companyTestDetails = companyTestDetails;
this.companyProdDetails = companyProdDetails;
this.hlagTestDetails = hlagTestDetails;
this.hlagProdDetails = hlagProdDetails;
testString = "testStringShowsUp";
}
public AS2() {
super();
}
public void setHlagProdDetails(AS2Details hlagProdDetails) {
this.hlagProdDetails = hlagProdDetails;
}
#Override
public String toString() {
return "AS2 Technology: Company: " + getCompanyTestDetails() + " / " + getCompanyProdDetails() + " HLAG: " +
getHlagTestDetails() + " " + getHlagTestDetails() + getTestString();
}
public AS2Details getCompanyTestDetails() {
return companyTestDetails;
}
public void setCompanyTestDetails(AS2Details companyTestDetails) {
this.companyTestDetails = companyTestDetails;
}
public AS2Details getCompanyProdDetails() {
return companyProdDetails;
}
public void setCompanyProdDetails(AS2Details companyProdDetails) {
this.companyProdDetails = companyProdDetails;
}
public AS2Details getHlagTestDetails() {
return hlagTestDetails;
}
public void setHlagTestDetails(AS2Details hlagTestDetails) {
this.hlagTestDetails = hlagTestDetails;
}
public AS2Details getHlagProdDetails() {
return hlagProdDetails;
}
public String getTestString() {
return testString;
}
public void setTestString(String testString) {
this.testString = testString;
}
}
AS2Details
import javax.persistence.Column;
import javax.persistence.Embeddable;
public class AS2Details {
#Column(name = "ip1", insertable = false, updatable = false)
private String ip1;
#Column(name = "as1id1", insertable = false, updatable = false)
private String AS2ID1;
#Column(name = "url", insertable = false, updatable = false)
private String url;
#Column(name = "as2id2", insertable = false, updatable = false)
private String AS2ID2;
#Column(name = "port", insertable = false, updatable = false)
private String port;
#Column(name = "ip2", insertable = false, updatable = false)
private String ip2;
#Column(name = "as2sofware", insertable = false, updatable = false)
private String AS2Software;
#Column(name = "userid", insertable = false, updatable = false)
private String userId;
#Column(name = "password", insertable = false, updatable = false)
private String password;
public AS2Details(String ip1, String AS2ID1, String url, String AS2ID2,
String port, String ip2, String userId, String password, String AS2Software) {
this.ip1 = ip1;
this.AS2ID1 = AS2ID1;
this.url = url;
this.AS2ID2 = AS2ID2;
this.port = port;
this.ip2 = ip2;
this.userId = userId;
this.password = password;
this.AS2Software = AS2Software;
}
public AS2Details() {
}
public String getIp1() {
return ip1;
}
public void setIp1(String ip1) {
this.ip1 = ip1;
}
public String getAS2ID1() {
return AS2ID1;
}
public void setAS2ID1(String AS2ID1) {
this.AS2ID1 = AS2ID1;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getAS2ID2() {
return AS2ID2;
}
public void setAS2ID2(String AS2ID2) {
this.AS2ID2 = AS2ID2;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
public String getIp2() {
return ip2;
}
public void setIp2(String ip2) {
this.ip2 = ip2;
}
public String getAS2Software() {
return AS2Software;
}
public void setAS2Software(String AS2Software) {
this.AS2Software = AS2Software;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "AS2 Details: " + getAS2ID1() + getAS2ID2() + getIp1() + getIp2() + getPassword();
}
}
Data creation in CommandLineRunner
AS2Details companyTestDetails = new AS2Details("192.1.168.1",
"ctest", "localhost:8080", "ctest2", "90", "111.00.5.4",
"scharpe", "+9+992++*9*+", "AS3");
AS2Details companyProdDetails = new AS2Details("192.1.168.1",
"cp", "localhost:8080", "cp", "90", "111.00.5.4",
"scharpe", "+9+992++*9*+", "AS3");
AS2Details hlagTestDetails = new AS2Details("192.1.168.1",
"htest1", "localhost:8080", "htest2", "90", "111.00.5.4",
"scharpe", "+9+992++*9*+", "AS3");
AS2Details hlagProdDetails = new AS2Details("192.1.168.1",
"hp1", "localhost:8080", "hp2", "90", "111.00.5.4",
"scharpe", "+9+992++*9*+", "AS3");
AS2 as2 = connectionTechnologyDetailsRepository.save(new AS2(ConnectionTechnologyDetails
.ConnectionTechnologyName.AS2, senderReceiverIds,
companyTestDetails, companyProdDetails, hlagTestDetails, new AS2Details()));
logger.info("CommandLineRunner: connectionTechnology AS2: " + as2.toString());
Connection userAs2Connection = new Connection(userRepository.findByUsername("userman").get(),
contactDetailsRepository.findFirstByOrderByIdAsc(),
BusinessCase.CONSIGNEE,
as2);
connectionRepository.save(userAs2Connection);
Why are the embedded fields null and the fields of the superclass filled?
When I'm printing the fields from the constructor in the subclass, the data is correctly there. But it's not saved from the constructor to the embedded fields.
Solved by myself.
If you map multiple #Embedded fields of the same type (AS2Details), you have to overwrite their attributes. Otherwise you have a name conflict in your table because multiple columns would have the same name. For example in this case you would have column url and column url in the same single table .
Therefore I added an onverride annotation to each embedded field in AS2 class.
For example the first field:
#Embedded
#AttributeOverrides({
#AttributeOverride(name="ip1", column=#Column(name="companyTestDetails_ip1")),
#AttributeOverride(name="AS2ID1", column=#Column(name="companyTestDetails_as2id1")),
#AttributeOverride(name="url", column=#Column(name="companyTestDetails_url")),
#AttributeOverride(name="AS2ID2", column=#Column(name="companyTestDetails_as2id2")),
#AttributeOverride(name="port", column=#Column(name="companyTestDetails_port")),
#AttributeOverride(name="ip2", column=#Column(name="companyTestDetails_ip2")),
#AttributeOverride(name="userId", column=#Column(name="companyTestDetails_userId")),
#AttributeOverride(name="password", column=#Column(name="companyTestDetails_password")),
#AttributeOverride(name="AS2Software", column=#Column(name="companyTestDetails_as2software"))
})
private AS2Details companyTestDetails;

JsonMappingException with Arrays of object in spring-jpa

i get an error when i try to get an item from my dbms. following error
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.pharmawizardcabinet.core.entity.cabinet.Cabinet.listaFarmaci, could not initialize proxy - no Session (through reference chain: com.pharmawizardcabinet.web.beans.ResponseCabinet["cabinet"]->com.pharmawizardcabinet.core.entity.cabinet.Cabinet["listaFarmaci"])
this is my conteiner
#Entity
#Table(name = "Cabinet")
public class Cabinet implements Serializable {
private static final long serialVersionUID = 7311927404447970875L;
#Id
#Column(name = "Id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long Id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "cabinet")
private List<Farmaco> listaFarmaci;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "user")
private User user;
#Column(name = "timestamp")
#Temporal(TemporalType.DATE)
private Date setLastModified;
public Cabinet() {
}
#PostPersist
#PostUpdate
private void setLastUpdate() {
this.setLastModified = new Date();
}
public List<Farmaco> getListaFarmaci() {
return listaFarmaci;
}
public void setListaFarmaci(List<Farmaco> listaFarmaci) {
this.listaFarmaci = listaFarmaci;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Long getId() {
return Id;
}
public void setId(Long id) {
Id = id;
}
public Date getSetLastModified() {
return setLastModified;
}
public void setSetLastModified(Date setLastModified) {
this.setLastModified = setLastModified;
}
}
and this is the item
#Entity
#Table(name = "Farmaco")
public class Farmaco implements Serializable {
private static final long serialVersionUID = -152536676742398255L;
public Farmaco() {
// TODO Auto-generated constructor stub
}
#Column(name = "nome_farmaco")
private String nome;
#Column(name = "codice")
private String codice;
#Column(name = "azienda")
private String azienda;
#Id
#Column(name = "Id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long Id;
#Column(name = "scadenza")
#Temporal(TemporalType.DATE)
private Date scadenza;
#Enumerated(EnumType.STRING)
#Column(name = "posologia")
private Posologia posologia;
#Column(name = "quantita")
private Integer quantita;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "note")
private Note note;
#ManyToOne(cascade =CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "cabinet_id")
private Cabinet cabinet;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getCodice() {
return codice;
}
public void setCodice(String codice) {
this.codice = codice;
}
public String getAzienda() {
return azienda;
}
public void setAzienda(String azienda) {
this.azienda = azienda;
}
public Long getId() {
return Id;
}
public void setId(Long id) {
Id = id;
}
public Date getScadenza() {
return scadenza;
}
public void setScadenza(Date scadenza) {
this.scadenza = scadenza;
}
public Posologia getPosologia() {
return posologia;
}
public void setPosologia(Posologia posologia) {
this.posologia = posologia;
}
public Integer getQuantita() {
return quantita;
}
public void setQuantita(Integer quantita) {
this.quantita = quantita;
}
public Note getNote() {
return note;
}
public void setNote(Note note) {
this.note = note;
}
public Cabinet getCabinet() {
return cabinet;
}
public void setCabinet(Cabinet cabinet) {
this.cabinet = cabinet;
}
}
controller is this
#Component("managerCabinet")
public class ManagerCabinet {
private static Logger logger = Logger.getLogger(ManagerCabinet.class);
#PersistenceContext(name = "pwcabinet-jpa")
private EntityManager entityManager;
#Transactional
public Cabinet getCabinetByUser(User user) {
logger.debug("[getCabinetByUser] user: " + user.getId());
return _getCabinetByUser(user);
}
private Cabinet _getCabinetByUser(User user) {
logger.debug("[_getCabinetByUser] user: " + user.getId());
User find = entityManager.find(User.class, user.getId());
Query searchCabinetByUser = entityManager.createQuery("Select c from Cabinet c where c.user = :userId", Cabinet.class);
searchCabinetByUser.setParameter("userId", find);
Cabinet cabinetSearch = (Cabinet) searchCabinetByUser.getSingleResult();
cabinetSearch.setUser(find);
return cabinetSearch;
}
}
but i continue to get error.
if i use the annotation #JsonIgnore in this way
#JsonIgnore
public List<Farmaco> getListaFarmaci() {
return listaFarmaci;
}
they works, but i need this information in my result. how i solve it?
When your method private Cabinet _getCabinetByUser(User user) returns the Cabinet instance is then in the 'detached' state, viz. is no longer associated with a persistence context.
When an item is in a detached state non-eagerly fetched associations can longer be accessed.
As the default fetch for #OneToMany is Lazy then in your case
#OneToMany(cascade = CascadeType.ALL, mappedBy = "cabinet")
private List<Farmaco> listaFarmaci;
the field listaFarmaci can no longer be accessed once the loaded Cabinet is detached from the persistence context.
You have various means of dealing with this which would include:
Marking the field as being eagerly fetched (not good as will always be eagerly fetched regardless of whether required or not).
Forcing the persistence context to remain open until all processing is done typically referred to as the OpenSessionInView pattern (or anti-pattern) depending on your point of view: http://java.dzone.com/articles/open-session-view-design
Ensuring all data required for use case is initialized before detachment. There are various ways of achieving this:
Simply accessing the collection is some way e.g. by calling size() but this may not work with all JPA providers.
Specifying FETCH JOIN in your JPQL query which loads the Cabinet (although this has side effects). http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Join_Fetching

Accessing Object using JAXB in Spring-JPA application

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.

Multiple beans validation inside containing bean with different group interfaces

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 :)

Categories

Resources