I'm trying to access JPA Data with REST using spring-boot-starter-data-rest.
I want to use a different method than the CrudRepository has. But the framework responds with the following exception:
exception is org.springframework.data.repository.support.QueryMethodParameterConversionException: Failed to convert Brazil into hello.Country!] with root cause
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [#org.springframework.data.repository.query.Param hello.Country]
Endpoint
http://localhost:8080/rest/cities/search/findByCountry?name=Brazil
CityRepository.java
#RepositoryRestResource(collectionResourceRel = "cities", path = "cities")
public interface CityRepository extends CrudRepository<City, Long> {
List<City> findByCountry(#Param("name") Country country);
}
City.java
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#Entity
#Table(name = "City")
public class City implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#JsonInclude(value=Include.ALWAYS)
private long id;
#NotNull
#Column(name="name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "country_id")
#Embedded
private Country country;
protected City() {}
public City(long id, String nome) {
this.id = id;
this.name = nome;
}
public Country getCountry() {
return country;
}
Country.java
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#Entity
public class Country implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#NotNull
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.LAZY, mappedBy="country")
private Set<City> cities;
protected Country() {}
public Country(long id, String nome) {
this.id = id;
this.name = nome;
}
When I call http://localhost:8080/rest/cities/ , I get the list of cities normally. I setted the configureRepositoryRestConfiguration to config.setBasePath("/rest");
java.lang.String != hello.Country
In accordance with the documentation, we need to:
Use ResponseBody with consumes
Or create object from String like in REST hello world example
I did the following to resolve this issue, using a nativeQuery:
#Query(value = "SELECT * FROM City c JOIN Country co
ON c.country_id = co.id WHERE co.name
LIKE (CONCAT_WS('%',:name, '%'))", nativeQuery = true)
List<City> country(#Param("name") String name);
Related
I tried to select all columns from the table MAGICNOTIFY_CARD_INFO, so i wrote a code;
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MagicnotifyApplication.class, args);
MagicnotifyCardInfoRepository magicnotifyCardInfoRepository =
context.getBean(MagicnotifyCardInfoRepository.class);
magicnotifyCardInfoRepository.findAll();
//SpringApplication.run(MagicnotifyApplication.class, args);
}
and this is the entity i wanted to select;
public class MagicnotifyCardInfoID implements Serializable {
#Column(name = "koname")
private String koname;
#Column(name = "name")
private String name;
#Column(name = "cardkingdom")
private String cardkingdom;
#Column(name = "cardkingdomfoil")
private String cardkingdomfoil;
#Column(name = "set")
private String set;
#Column(name = "setName")
private String setName;
#Column(name = "reldate")
private Date reldate;
#Column(name = "rarity")
private String rarity;
#Column(name = "uuid")
private String uuid;
#ManyToOne
private MagicnotifyUuidName magicnotifyUuidName;
#ManyToOne
private MagicnotifySetInfo magicnotifySetInfo;
}
public class MagicnotifyCardInfo implements Serializable {
#EmbeddedId
private MagicnotifyPriceID id;
}
public interface MagicnotifyCardInfoRepository extends JpaRepository<MagicnotifyCardInfo, Long> {
#Query(value = "SELECT * FROM MAGICNOTIFY_CARD_INFO", nativeQuery = true)
List<MagicnotifyCardInfo> findByAll();
List<MagicnotifyCardInfo> findAll();
}
but after querying, it tries to select other column item from table
MAGICNOTIFY_PRICE;
public class MagicnotifyPriceID implements Serializable {
#Column(name = "foil")
private BigDecimal foil;
#Column(name = "normal")
private BigDecimal normal;
#Column(name = "date")
private Date date;
#Column(name = "key")
private String key;
#ManyToOne
private MagicnotifyUuidName id;
}
public class MagicnotifyPrice implements Serializable {
#EmbeddedId
private MagicnotifyPriceID id;
}
I'm not sure why it happens from differently mapped two tables; how can i select from initial table MAGICNOTIFY_CARD_INFO and select from its columns?
First of all, you have not mentioned any primary key using #Id annotation inside either of your MagicnotifyCardInfoID class or MagicnotifyPriceID class
Secondly, you have given same #EmbeddedId fields "MagicnotifyPriceID id" in both the below classes
public class MagicnotifyCardInfo implements Serializable {
#EmbeddedId
private MagicnotifyPriceID id;
}
public class MagicnotifyPrice implements Serializable {
#EmbeddedId
private MagicnotifyPriceID id;
}
I don't see #Embeddable used anywhere in your program
Please refer https://www.baeldung.com/jpa-embedded-embeddable
public interface MagicnotifyCardInfoRepository extends JpaRepository<MagicnotifyCardInfo, Long> {
#Query(value = "SELECT * FROM MAGICNOTIFY_CARD_INFO", nativeQuery = true)
List<MagicnotifyCardInfo> findByAll();
List<MagicnotifyCardInfo> findAll();
}
In the above class you are passing "JpaRepository<MagicnotifyCardInfo, Long>"
Long as the data type of a primary key in your entity "MagicnotifyCardInfo"
which does not even exist.
Please fix these and try again.
I have two models that are having one to many relation (customers have many invoices)
so i create one - many relation on it, this is my customer class :
#Entity
#Table(name = "customer")
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "is_deleted")
private boolean isDeleted;
#OneToMany
private Set <Invoice> invoices;
}
and this is invoices class :
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "status")
private String status;
#Column(name = "created_date")
private Timestamp createdDate;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
}
and then i create GET API ( get customers ) but it's nor working and return this error :
nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.example.invoices.model.Customer["invoices"]), path=/customer/viewList}]
and this is my api :
public List<Customer> getAllCustomers() {
List<Customer> customers = cutomerRepository.findAll();
return customers;
}
and controller :
#GetMapping("/viewList")
public ResponseEntity<List<Customer>> getAllCustomers() {
List<Customer> customers = new ArrayList<>();
customers = customerService.getAllCustomers();
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers, HttpStatus.OK);
}
You have a Bidirectional relation and therefore an endless loop if json tries to deserialize the Object.
You can use #JsonIgnore to break the loop or use DTOs to return at the endpoint
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "status")
private String status;
#Column(name = "created_date")
private Timestamp createdDate;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JsonIgnore
#JoinColumn(name = "customer_id")
private Customer customer;
}
DTO would look something like this (I like to use records for this but since I don't know if you use Java 17 I still use class):
Customer:
#Data
public class CustomerDTO {
private final int id;
private final long serialNumber;
private final String firstName;
private final String lastName;
private final String email;
private final String mobileNumber;
private final boolean isDeleted;
private final Set <Invoice> invoices;
public static CustomerDTO fromModel(Customer customer) {
return new CustomerDTO(
customer.getId(),
customer.getSerialNumber(),
customer.getFirstName(),
customer.getLastName(),
customer.getEmail(),
customer.getMobileNumber(),
customer.isDeleted(),
customer.getInvoices()
.stream()
.map(InvoiceDTO::fromModel)
.collect(Collectors.toSet())
);
}
}
Invoice (here you don't show the customer again):
#Data
public class InvoiceDTO {
private final int id;
private final String status;
private final Timestamp createdDate;
private final boolean isDeleted;
public static InvoiceDTO fromModel(Invoice invoice) {
return new InvoiceDTO(
invoice.getId(),
invoice.getStatus(),
invoice.getCreatedDate(),
invoice.isDeleted()
);
}
}
Controller:
#GetMapping("/viewList")
public ResponseEntity<List<CustomerDTO>> getAllCustomers() {
List<CustomerDTO> customers = new ArrayList<>();
customers = customerService.getAllCustomers()
.stream()
.map(CustomerDTO::fromModel)
.toList() //Depending on Java Version .collect(Collectors.toList());
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers., HttpStatus.OK);
}
Do not open the entity class directly to the outside world
As DTO use for example:
public class InvoiceDTO {
private int id;
private long serialNumber;
private String status;
private Timestamp createdDate;
private boolean isDeleted;
private CustomerDTO customer;
}
See it applied in my GitHub repo FurnitureStoreApplication, example DTO classes in package dto:
I want to create a Person with an Address, each of them are an entity. My Entities seem to work, the part where i begin to struggle is on how to create a Person using the constructor where i also have to put in the Address.
personRepository.save(new Person(new Name("Test","Test"),new Adress("Street","Number","PLZ","Town"),LocalDate.parse("2000-01-01"),"email#email.com","911");
This sadly does not work so my question is how can i create a Person object with the Address.
I'm also wondering how i would add the address if i already got the address in my Address repository, is there a way to get the address or use the adress ID?
adresseRepository.save(new Adresse("Street","Number","PLZ","Town"));
Here's the code for both of the shortend.
Person:
#Entity
#Table(name = "Person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PersonID")
private Long personID;
#Column(name = "FullName")
#Convert(converter = NameConverter.class)
private Name fullName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="AdresseID")
private Adresse adresse;
#Column(name = "Geburtsdatum")
private LocalDate geburtsdatum;
#Column(name = "EMail")
private String email;
#Column(name = "Telefonnummer")
private String telefonnummer;
private Person() {}
public Person(Name fullName, Adresse adresse, LocalDate geburtsdatum, String email, String telefonnummer) {
this.fullName = fullName;
this.adresse = adresse;
this.geburtsdatum = geburtsdatum;
this.email = email;
this.telefonnummer = telefonnummer;
}
}
Address:
#Entity
#Table(name = "Adresse")
public class Adresse {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "AdresseID")
private Long adresseID;
#Column(name = "Strasse")
private String strasse;
#Column(name = "Hausnummer")
private String hausnummer;
#Column(name = "PLZ")
private String plz;
#Column(name = "Ort")
private String ort;
protected Adresse() {}
public Adresse(String strasse, String hausnummer, String plz, String ort) {
this.strasse = strasse;
this.hausnummer = hausnummer;
this.plz = plz;
this.ort = ort;
}
}
Ralationships are created in hibernate like this:
#Entity
#Table(name="CART")
public class Cart {
//...
#OneToMany(mappedBy="cart")
private Set<Item> items;
// getters and setters
}
Please note that the #OneToMany annotation is used to define the property in Item class that will be used to map the mappedBy variable. That is why we have a property named “cart” in the Item class:
#Entity
#Table(name="ITEMS")
public class Item {
//...
#ManyToOne
#JoinColumn(name="cart_id", nullable=false)
private Cart cart;
public Item() {}
// getters and setters
}
Soin your case you just have to add
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
//...
#OneToOne(mappedBy = "address")
private User user;
something lilke this to your Adress Table.
Because one Adress also have one user.
For more information visit this site
I have encountered this exception
Caused by:
org.hibernate.MappingException: Unable to find column with logical name: picture_id in org.hibernate.mapping.Table(TXN_INVENTORY_PICTURE) and its related supertables and secondary tables
at org.hibernate.cfg.Ejb3JoinColumn.checkReferencedColumnsType(Ejb3JoinColumn.java:564)
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:258)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:116)
at org.hibernate.cfg.Configuration.processEndOfQueue(Configuration.java:1596)
at org.hibernate.cfg.Configuration.processFkSecondPassInOrder(Configuration.java:1519)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1420)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1844)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:850)
Here's a gist of the classes that are involve
#Entity
#Table(name = "REF_PRODUCT")
public class Product extends JpaModel{
#Column(name= "NAME")
private String name;
#Column(name = "MANUFACTURER")
private String manufacturer;
/**
* Work around for missing
*/
#OneToOne(optional = true)
#JoinColumn(name="picture",referencedColumnName = "picture_id")
private InventoryItemPicture picture;
}
And here's The inventoryItemPicutre
#Entity
#Table(name = "TXN_INVENTORY_PICTURE")
public class InventoryItemPicture extends JpaModel{
#Column
private byte[] image;
#Column
private String fileName;
public InventoryItemPicture(){
}
....
}
And here's the JPAModel
#MappedSuperclass
public abstract class JpaModel {
#Id
#Column(name ="ID", columnDefinition = "serial")
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDateTime")
#Column(name = "created_date")
private DateTime createdDate;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public DateTime getCreatedDate() {
return createdDate;
}
public void setCreatedDate(DateTime createdDate) {
this.createdDate = createdDate;
}
}
NOTE: Omitted the setters and getters,and what I am trying to achieve here is The picture can be null in the Product class
You have an error in your mapping, referencedColumnName targets the column picture_id in TXN_INVENTORY_PICTURE table, which doesn't exist.
If picture_id is the name of the column in REF_PRODUCT table which is a foreign key to TXN_INVENTORY_PICTURE table, then change the mapping to this
#OneToOne(optional = true)
#JoinColumn(name="picture_id")
private InventoryItemPicture picture;
I am using Hibernate3 and i have an entity with the following collection:
#ManyToMany
#JoinTable(name = "buys_publishers", joinColumns=#JoinColumn(name="buy_id", referencedColumnName = "buy_id"), inverseJoinColumns=#JoinColumn(name = "publisher_id", referencedColumnName = "publisher_id"))
#OrderBy("name")
private List<Publisher> publishers;
(fetch and cascade decelerations omitted)
The target entity (Publisher) inherits from an entity that holds the "name" attribute on which the #orderby is activated.
here is the target entity:
#Entity
#Table(name="publishers")
#PrimaryKeyJoinColumn(name="account_id")
public class Publisher extends Account{
/**
*
*/
private static final long serialVersionUID = 1L;
#Column(name = "publisher_id")
private Long publisherId;
public Long getPublisherId() {
return publisherId;
}
public void setPublisherId(Long publisherId) {
this.publisherId = publisherId;
}
}
and the super class:
#Entity
#Table(name="accounts")
#Inheritance(strategy=InheritanceType.JOINED)
public abstract class Account implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#Column(name="id",unique=true, nullable=false )
#GeneratedValue( strategy = IDENTITY )
private long id;
#Column(name = "name")
private String name;
#Column(name = "account_type")
#Enumerated(EnumType.ORDINAL)
private AccountType accountType;
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;
}
public AccountType getAccountType() {
return accountType;
}
public void setAccountType(AccountType accountType) {
this.accountType = accountType;
}
}
the query that Hibernate generates is:
select publishers0_.buy_id as buy1_1_, publishers0_.publisher_id as publisher2_1_, publisher1_.account_id as id6_0_, publisher1_1_.account_type as account2_6_0_, publisher1_1_.name as name6_0_, publisher1_.publisher_id as publisher1_18_0_ from buys_publishers publishers0_ left outer join publishers publisher1_ on publishers0_.publisher_id=publisher1_.publisher_id left outer join accounts publisher1_1_ on publisher1_.account_id=publisher1_1_.id where publishers0_.buy_id=? order by accounts.name asc
it is clear from the query that the order by should be on publisher1_1_, am i doing something wrong or is this a bug?
Thank you.
This looks like HHH-4260 - #OrderBy does not work with inherited properties (and I wouldn't expect a short term resolution).