I have defined customer entity
#Entity
#Table(name = "customer")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
and CrudRepository
public interface CustomerRepo extends CrudRepository<Customer, Long> {
}
if I use CustomerRepo.findById method for finding Customer
#Autowired
CustomerRepo repo;
Optional<Customer> dbCustomer = repo.findById(id);
how can I get name of that customer. I cannot use getter then.
so I'm interested is there any solution of using getters of Optional, or I need to use other method for finding Customer by id?
Optional<Customer> is returned, because it is not guaranteed that there will be such a customer with the requested ID in the database.
Instead of returning null it simply means that Optional.isPresent() will return false when the ID does not exist.
According to the API Docs (https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#findById-ID-):
Returns:
the entity with the given id or Optional#empty() if none found
You will therefore probably want to simply use the methods on Optional to check whether it contains a Customer (i.e. a Customer with that ID exists), and then get the name like so:
Optional<Customer> dbCustomer = repo.findById(id);
if(dbCustomer.isPresent()) {
Customer existingCustomer = dbCustomer.get();
String nameWeWanted = existingCustomer.getName();
//operate on existingCustomer
} else {
//there is no Customer in the repo with 'id'
}
Alternatively you can try callback style (shown with Java 8 Lambda):
Optional<Customer> dbCustomer = repo.findById(id);
dbCustomer.ifPresent(existingCustomer -> {
String nameWeWanted = existingCustomer.getName();
//operate on existingCustomer
});
It is worth noting that it is possible to check existence of the ID without actually retrieving/loading the entity by using the interface method:
boolean CrudRepository.existsById(ID id)
This saves an entity load, but it still requires a database roundtrip.
Try to use another method for finding Customer:
#Autowired
CustomerRepo repo;
Customer dbCustomer = repo.findOne(id);
Related
I have two classes one for Customers and one for Transaction. In the transaction, I have a field custID(int) which is also present in Customer. I have all the getter and setter, repos, services, and controller as well. But in one of my methods in the service layer, I am getting NoSuchElementException.
I understand that while the code runs and checks for a record in the database with passed custID, it cannot find the record. But I have mentioned what to do in such case. But my code doesn't move to the code block at all.
Customer Class
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int custID;
private String custName;
private String email;
private String phone;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private LocalDate registrationDate;
Transaction Class
#Entity
#Table(name = "transactions")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int transID;
private int custID;
private int transAmount;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private LocalDate transDate;
Service layer from where the issue arises:
TransactionServiceImpl
#Override
public Transaction addTransaction(CustomerTransaction customerTransaction) {
Customer customer = customerTransaction.getCustomer();
Transaction transaction = customerTransaction.getTransaction();
if(customerService.findCustomerByID(transaction.getCustID()) == null) {
customerService.addCustomer(customer);
return transactionRepository.save(transaction);
} else{
return transactionRepository.save(transaction);
}
}
What I am doing is, pass a wrapper object CustomerTransaction that has the info for the customer and transaction. And check if the customer is already registered with custID. If its there, it only records the transaction(which works fine as in 'else' block). But if it is not there I want to record the customer and the transaction both as in 'if' block. But it throws the NoSuchElementException: No value present.
But if I am to pass only the customer details via customer's service layer it adds the customer.
CustomerServiceImpl
#Override
public Customer addCustomer(Customer customer) {
return customerRepository.save(customer);
}
Postman Requests:
For customer only:
"custName": "Bibek Bhattarai",
"email": "spongebob#gmail.com",
"phone": 9803064423,
"registrationDate": "03-01-2023"
}
For customerTransaction:
{
"customer":{
"custName": "Sponge Bob",
"email": "spongebob#gmail.com",
"phone": 9803064423,
"registrationDate": "03-01-2023"
},
"transaction":{
"custID":9,
"transAmount": 5000,
"transDate": "04-01-2023"
}
}
You may get this exception because the value returned by findCustomerByID() is an Optional of Customer (Optional<Customer>) and not null.
Instead of customerService.findCustomerByID(transaction.getCustID()) == null you should have customerService.findCustomerByID(transaction.getCustID()).isEmpty().
I have a problem with my Club entity - I'm using LAZY fetch type and ModelMapper to return my JSON. The problem is that if I use LAZY instead of EAGER what I get as a response of GET /api/players/{id} is:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: could not initialize proxy
and a screenshot from Postman:
When I debug my controller's action:
#GetMapping("/api/players/{id}")
ResponseEntity<PlayerDto> getPlayer(#PathVariable String id) {
Player foundPlayer = playerInterface.getPlayer(Long.valueOf(id));
PlayerDto playerToDto = convertToDto(foundPlayer);
return ResponseEntity.ok().body(playerToDto);
}
...
private PlayerDto convertToDto(Player player) {
return modelMapper.map(player, PlayerDto.class);
}
it seems like both foundPlayer and playerToDto have the Club like this:
but when I do foundPlayer.getClub().getName() I get a proper name. I know it's probably expected behavior, but I would love to have the Club returned in my response like this (screenshot from the response if EAGER is set):
without having to set the fetch type to EAGER.
My Player entity:
#Entity
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.EAGER)
#JsonManagedReference
private Club club;
My Club entity:
#Entity
public class Club {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "club", cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JsonBackReference
private List<Player> players;
getPlayer method from the PlayerService (the one, that the controller calls):
#Override
public Player getPlayer(Long id) {
Optional<Player> foundPlayer = playerRepository.findById(id);
return foundPlayer.orElseThrow(PlayerNotFoundException::new);
}
PlayerToDto:
package pl.ug.kchelstowski.ap.lab06.dto;
import pl.ug.kchelstowski.ap.lab06.domain.Club;
public class PlayerDto {
private Long id;
private String firstName;
private String lastName;
private Club club;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Club getClub() {
return club;
}
public void setClub(Club club) {
this.club = club;
}
}
You're right, this is the expected behavior of lazy loading. It's a good thing, don't set it to eager! Instead of returning a Club #Entity class directly on your response body, you should create a ClubDto and initialize it with another convertToDto method. It's kinda tedious (I like using Mapstruct and Lombok to alleviate that), but it'll induce Hibernate to make all the queries you need.
#Data
public class ClubDto {
private String id;
private String name;
}
#Mapper
public interface ClubMapper {
public ClubDTO mapToDto(Club club);
}
Oops, didn't realize you were already using ModelMapper. I'm not too familiar with that, but it sounds like it will just work if you swap Club for ClubDto.
I have a solution guys, but I'd like to hear from you if it can be done this way, or it is some kind of anti-pattern.
I just simply set the playerToDto's Club to the brandly new fetched Club with the ID of the foundPlayer
#GetMapping("/api/players/{id}")
ResponseEntity<PlayerDto> getPlayer(#PathVariable String id) {
Player foundPlayer = playerInterface.getPlayer(Long.valueOf(id));
PlayerDto playerToDto = convertToDto(foundPlayer);
playerToDto.setClub(clubInterface.getClub(foundPlayer.getClub().getId()));
return ResponseEntity.ok().body(playerToDto);
}
In the end I came up with this:
#GetMapping("/api/players")
ResponseEntity<List<PlayerDto>> getAllPlayers() {
List<PlayerDto> playersList = playerInterface.getAllPlayers().stream().map(this::convertToDto).collect(Collectors.toList());
playersList.forEach(playerInterface::fetchClubToPlayer);
return ResponseEntity.ok().body(playersList);
}
#GetMapping("/api/players/{id}")
ResponseEntity<PlayerDto> getPlayer(#PathVariable String id) {
Player foundPlayer = playerInterface.getPlayer(Long.valueOf(id));
PlayerDto playerToDto = convertToDto(foundPlayer);
playerInterface.fetchClubToPlayer(playerToDto);
return ResponseEntity.ok().body(playerToDto);
}
public PlayerDto fetchClubToPlayer(PlayerDto player) {
if (player.getClub() != null) {
Club club = clubInterface.getClub(player.getClub().getId());
player.setClub(club);
}
return player;
}
is it fine?
I suggest you use #EntityGraph to configure the fetch plan of the resulting method's query. For example, you can declare a method in PlayerRepository to find a Player entity by id, apart from the default findById method, where its Club entity would be fetched eagerly.
public interface PlayerRepository extends JpaRepository<Player, Long>{
...
#EntityGraph(attributePaths = {"club"})
Optional<Player> findWithClubFetchedEagerlyById(LongId);
}
By providing attributePaths, fields that should be fetched eagerly are defined.
If the Club entity should be always fetched eagerly when you call the findById method then there's no need for a separate method, therefore you can annotate the default one with #EntityGraph.
With this solution, the network traversal is minimized because all needed data is fetched at once from a database.
I'm trying to implement a reactive(r2dbc) repository in Micronaut but I'm having some problems with the data that is being queried. Those issues don't occur when using non-reactive repositories.
Here's how my reactive repository looks:
#R2dbcRepository(dialect = Dialect.MYSQL)
public interface ReactiveCampaignRepository extends ReactiveStreamsCrudRepository<Campaign, Integer> {
#Override
Flux<Campaign> findAll();
}
And this is how my regular repository looks:
#Repository
public interface CampaignRepository extends CrudRepository<Campaign, Integer> {
}
When invoking findAll method from ReactiveCampaignRepository I'm able to query all entities, however all of them have null ids.
When I invoke findAll from CampaignRepository all entites are queried and Ids are populated correctly.
This is how id field looks in Campaign, which is a remote dependency
#Entity
#Table(name = "campaign")
public class Campaign implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
This entity is introspected like this:
#Introspected(classes = {Campaign.class})
public class EntitiesConfiguration {
}
I'm new to micronaut and r2dbc so I could be missing something obvious but I cannot figure it out and any pointers would be greatly appreciated.
Thank You
EDIT:
#tmarouane It's just a simple controller to test if things are working as they should.
#Get(value = "/all")
public Flux<CampaignDTO> allCampaigns() {
return reactiveCampaignRepository.findAll().map(CampaignDTO::new);
}
#Get(value = "/all2")
public List<CampaignDTO> allCampaigns2() {
return StreamSupport.stream(campaignRepository.findAll().spliterator(), false).map(CampaignDTO::new).collect(Collectors.toList());
}
and controller
#Produces(MediaType.APPLICATION_JSON)
#Secured(SecurityRule.IS_AUTHENTICATED)
#Controller("/campaign")
public class CampaignController {
private final CampaignRepository campaignRepository;
private final ReactiveCampaignRepository reactiveCampaignRepository;
public CampaignController(CampaignRepository campaignRepository,
ReactiveCampaignRepository reactiveCampaignRepository
) {
this.campaignRepository = campaignRepository;
this.reactiveCampaignRepository = reactiveCampaignRepository;
}
CampaignDTO is just a simple DTO class where just a subset of Campaign's fields are used, with a simple constructor taking Campaign object.
public CampaignDTO(Campaign campaign) {
this.id = campaign.getId();
}
Besides id there's 1 more attribute which is not null but it's own attributes are null which I haven't spotted at first - customer, even though customer_id is populated in objects queried with both reactive and non reactive repos, here's how it looks in Campaign
#JoinColumn(name = "customer_id", referencedColumnName = "customer_id")
#ManyToOne(optional = false)
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
This seems to be solved in micronaut 3.0.1 but it doesn't work in 3.0.2
I have an EJB many-to-many (bi-directional) relation between classes (entity-classes) Person and Hobby. There are corresponding tables in the database, called PERSON and HOBBY, as well as a table PERSON_HOBBY for the many-to-many relationship.
As I will detail below, the problem is that whenever I try to persist a person with hobbies, I run into a Foreign Key constraint violation. This is because the entityManager tries to save new rows into PERSON_HOBBY that contain references to a person-entity with ID=0, which doesn’t exist in the PERSON table. I’ll come back to that later, but first I’ll show the relevant parts of the entity classes.
First, here is entity class Person:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String email;
#ManyToMany(cascade = {CascadeType.MERGE}, fetch = FetchType.EAGER)
/* Note: I used to have CascadeType.PERSIST in the above line as well, but
it caused "Detached object passed to persist" exceptions whenever I tried to
persist a person with hobbies. So I suppose I was right in deleting
CascadeType.PERSIST...? */
#JoinTable(name = "PERSON_HOBBY",
joinColumns = #JoinColumn(name="personId", referencedColumnName="id"),
inverseJoinColumns = #JoinColumn(name="hobbyId", referencedColumnName="id"))
private List<Hobby> hobbies = new ArrayList<Hobby>();
public List<Hobby> getHobbies () {
return hobbies;
}
public void setHobbies (List<Hobby> hobbies) {
this.hobbies = hobbies;
for(Hobby h:hobbies) { // this is to maintain bi-directionality
h.addPerson(this);
}
}
// other getters and setters omitted here.
Then entity class Hobby:
#Entity
public class Hobby {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String description;
#ManyToMany(mappedBy = "hobbies", fetch = FetchType.EAGER)
private List<Person> persons;
public Hobby() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
// getter and setter for Description omitted here.
public List<Person> getPersons () {
return persons;
}
public void setPersons (List<Person> persons) {
this.persons = persons;
}
public void addPerson (Person p) {
this.persons.add(p);
}
}
I also have a stateless session bean, that’s shown here as far as relevant to the issue:
#Stateless
#Default
public class PersonRepositoryImpl implements PersonRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public Person create(Person p) {
entityManager.persist(p);
entityManager.flush();
return p;
}
#Override
public Person createPersonWithHobbies(Person p, List<Hobby>hobbyLijst) {
p = create(p); // I've also tried simply: create(p);
System.out.println("The newly assigned ID for the persisted
person is: " + p.getId());
// That last line **always** prints the person-ID as being 0 !!!!
p.setHobbies(hobbyLijst);
entityManager.merge(p); // This should save/persist the person's hobby's!
entityManager.flush();
return p;
}
}
Now from my servlet, I've been trying in two different ways. First, I tried calling method create(p) on the above session bean. That is, after creating a new Person instance p, setting all its non-relational fields, AND calling setHobbies on it (with a non-zero list of Hobby objects taken from the database), I called:
personRepo.create(p);
But this resulted in the Foreign Key (FK) exception:
INSERT on table 'PERSON_HOBBY' caused a violation of foreign key
constraint 'FK_EQAEPVYK583YDWLXC63YB3CXK' for key (0). The statement
has been rolled back.”
The FK-constraint mentioned here is the one in PERSON_HOBBY referring to PERSON.
The second way I tried was to make the following call from the servlet:
personRepo.createPersonWithHobbies(p, hobbyLijst);
where, just like before, p is the new person object; and hobbyLijst is that person's list of hobbies. And this resulted in the exact same FK-exception as the earlier call to personRepo.create(p).
Importantly, the println statement within method createPersonWithHobbies, calling getId() on the newly persisted person-object, ALWAYS gives that object's ID as being 0. Which I suppose does explain the FK-exception, since there's no person entity/row in the PERSON table with an ID of 0, nor is there supposed to be one. But of course the getId() call should not output 0. Instead, it should output the newly generated ID of the newly persisted person entity. (And yes, it IS persisted correctly in the PERSON tabel, with a correctly generated ID>0. So the correct ID is there in the PERSON-table - it just seems to be invisible to the entityManager and/or the container.)
Thanks.
pals.
I have an issue with Hibernate's JPA implementation. I use spring-boot-starter-data-jpa and PostgreSql v9.
I have two entities with bidirectional connection via OneToMany & ManyToOne:
#Entity
public class ShoppingCart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "shoppingCart", cascade = {CascadeType.ALL})
private List<Good> goods = new ArrayList<>();
public void addGood(Good good) {
good.setShoppingCart(this);
goods.add(good);
}
public Good removeGood(Good good) {
goods.remove(good);
good.setShoppingCart(null);
return good;
}
public ShoppingCart() {
}
public List<Good> getGoods() {
return goods;
}
public ShoppingCart(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
And second entity is
#Entity
public class Good {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cart_id")
#JsonIgnore
private ShoppingCart shoppingCart;
public ShoppingCart getShoppingCart() {
return shoppingCart;
}
public void setShoppingCart(ShoppingCart shoppingCart) {
this.shoppingCart = shoppingCart;
}
public Good(String name) {
this.name = name;
}
public Good() {
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
Also I use CrudRepository to access ShoppingCart
public interface ShoppingCartRepository extends CrudRepository<ShoppingCart, Long> {}
And when I'm trying to fill existing cart I have two goods in my database. This is a code to add some goods into existing cart:
ShoppingCart cart = shoppingCartRepository.findOne(id);
cart.addGood(new Good("Butter"));
return shoppingCartRepository.save(cart);
In table "good" I have now two elements with different PKey and same data
5;"Butter";100
6;"Butter";100
Why it happens?
Also, when I'm trying to insert breakpoint at repository.save line, I see only one good in goods list in cart.
So, the problem is solved.
First way to solve is to make method with save code #Transactional.
Secon way is to use getGoods() instead of goods. We should change this code
public void addGood(Good good) {
good.setShoppingCart(this);
goods.add(good);
}
public Good removeGood(Good good) {
goods.remove(good);
good.setShoppingCart(null);
return good;
}
to this
public void addGood(Good good) {
good.setShoppingCart(this);
this.getGoods().add(good);
}
public Good removeGood(Good good) {
this.getGoods().remove(good);
good.setShoppingCart(null);
return good;
}
getGoods() here forces hibernate to update state of object and everything works fine.
As for me, I use both ways together
It happens because you create a new Good object without id. So Hibernate will generate a new id and persist the new object. If you don't want to create a new object, but only assign an already existing one, you either have to fetch the existing one from the database and assign it to the ShoppingCart oder add the ID if you create the new Good object.