Spring Boot Maven and MySQL Error Handling - mysql

I have the following Spring Boot MySQL query:
visitorrepository.save(newvisitor)
Upon execution of this MySQL query, I want to return a success JSON or failure JSON in the following format:
Success State:
{
"success": true,
"message": "Some Helpful Message",
"data": { } //This would be the newvisitor JSON that includes the primary key (id)
}
Failure State:
{
"success": false,
"message": "Some Helpful Message",
"error_code": "404", // This should be whatever error number was returned
"data": { } //This would be the newvisitor JSON that does not include the primary key (id)
}
In angular the response is captured as follows
this.http.post('http://localhost:8080/v1/api/post', this.visitor.value).toPromise().then((response:any) => {
console.log(response);
})
post.java
#CrossOrigin(origins = "http://localhost:4200")
#RestController
public class post {
#Autowired
visitorrepository visitorrepository;
#PostMapping("/v1/api/post")
public void insert(#Valid #RequestBody newvisitor newvisitor) {
try {
visitorrepository.save(newvisitor);
return // Success State JSON
} catch () {
return // Error State JSON
}
}
}
newvisitor.java
#Getter
#Setter
#Entity
#Table(name = "visitors")
public class newvisitor {
#Id
#GeneratedValue
private Long id;
#Size(min=1, max=250)
#NotBlank
private String firstname;
#NotBlank
private String lastname;
#NotBlank
private String month;
#NotBlank
private String day;
#NotBlank
private String year;
#NotBlank
private String socialsecuritynumber;
#NotBlank
private String street1;
private String street2;
#NotBlank
private String city;
#NotBlank
private String state;
#NotBlank
private String zip;
#NotBlank
private String phone;
#Email
#NotBlank
private String email;
public newvisitor(){
super();
}
public newvisitor(String firstname, String lastname, String month, String day, String year, String socialsecuritynumber, String street1, String street2, String city, String state, String zip, String phone, String email) {
super();
this.firstname = firstname;
this.lastname = lastname;
this.month = month;
this.day = day;
this.year = year;
this.socialsecuritynumber = socialsecuritynumber;
this.street1 = street1;
this.street2 = street2;
this.city = city;
this.state = state;
this.zip = zip;
this.phone = phone;
this.email = email;
}
}
visitorrepository.java
#Repository
public interface visitorrepository extends CrudRepository<newvisitor, Long> {
}
The thought is to catch everything from MySQL database not connecting, to invalid data entry, to duplicate records, bascially anything that prevented the initial query visitorrepository.save(newvisitor) from working, and return this to angular as a JSON. I feel like ResponseEntity or RestControllerAdvice might be the answer, just not sure best way to implement if this is accurate.

You already have the answer.
I feel like ResponseEntity or RestControllerAdvice might be the answer
You only create a dto response with your format. then put it in ResponseEntity in case error or not.

Related

Many-To-One : JSON parse error Cannot construct instance

I devleloping a REST API using spring boot
I have two entities with bidirectional OneToMany
Product class
public class Product {
private Long productId;
private String name;
private String description;
private List<ProductList> productList;
public Product() {
}
public Product(Long productId, String name, String description, List<ProductList> productList) {
this.productId = productId;
this.name = name;
this.description = description;
this.productList = productList;
}
public Long getProductId() {
return productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public List<ProductList> getProductList() {
return productList;
}
public void setProductList(List<ProductList> productList) {
this.productList = productList;
}
ProductList class
public class ProductList {
private Long productListId;
private String productListName;
private Product product;
public ProductList(Long productListId, String productListName, Product product) {
this.productListId = productListId;
this.productListName = productListName;
this.product = product;
}
public ProductList() {
}
public Long getProductListId() {
return productListId;
}
public void setProductListId(Long productListId) {
this.productListId = productListId;
}
public String getProductListName() {
return productListName;
}
public void setProductListName(String productListName) {
this.productListName = productListName;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
}
ProductEntity class:
#Entity
#Data
#Table(name = "PRODUCT")
public class ProductEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private Integer productId;
#Column
private String name;
#Column
private String description;
#OneToMany(cascade = CascadeType.ALL, mappedBy="product", fetch =
FetchType.EAGER)
private List<ProductListEntity> productList;
// Getters,Setters,No-ArgCOnstructor, All-ArgConstructor
ProductListEntity class:
#Table
#Entity
#Data
public class ProductListEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long productListId;
private String productListName;
#ManyToOne(fetch = FetchType.EAGER, targetEntity = ProductEntity.class)
#JoinColumn(name = "FK_Product", referencedColumnName = "productId")
private ProductEntity product;
Service to save data:
public void addProduct(Product product) {
ProductEntity productEntity = new ProductEntity();
BeanUtils.copyProperties(product, productEntity);
productRepository.save(productEntity);
}
When i try to post I get this error:
"message": "JSON parse error: Cannot construct instance of
eteosf.hexagonal.domain.model.Product (although at least one Creator
exists): no String-argument constructor/factory method to deserialize
from String value ('string'); nested exception is
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of eteosf.hexagonal.domain.model.Product
(although at least one Creator exists): no String-argument
constructor/factory method to deserialize from String value
('string')\n at [Source: (PushbackInputStream); line: 9, column: 18]
(through reference chain:
eteosf.hexagonal.domain.model.Product["productList"]->java.util.ArrayList[0]->eteosf.hexagonal.domain.model.Product$ProductList["product"])",
"path": "/product"
JSON request body:
{
"productId": 0,
"name": "string",
"description": "string",
"productList": [
{
"productListId": 0,
"productListName": "string",
"product": "string"
}
]
}
in your json_request, inside of productList you are sending "product" as a "string". But Deserializer can not turn that string into a Product object. It has to be sent as object {}. You can leave that object empty - just not send it if all it does is point at itself.
You basically have made a mistake of confusing different principles - the whole bidirectional relationship is only to be applied at persistence level. Json requests are being sent at controller/view level and therefore you can't display the bidirectional nature in the same way. This is why you don't use Entities as controller params but use DTOs.
In your case just don't send the "product" field for the controller:
{
"productId": 0,
"name": "string",
"description": "string",
"productList": [
{
"productListId": 0,
"productListName": "string",
}
]
}
and just add it in the controller method right after receiving the parameter:
//the receiving controller method which got Parameter `ProductEntity product`
product.getProductList().forEach(productList -> productList.setProduct(product);
Like I said you shouldn't use entities in Controller method, and it should be a DTO class in order to avoid exactly this kind of issues

How to solve HHH000346 Error using hibernate 5 and mysql?

I'm studying restful service and views.
Regarding it, I use mysql and hibernate 5.
My data tables are two and have reference relation.
The problem occurs when I update the primary key.
When I add new one then update existing data in another table (they have reference relation), HHH000346: Error during managed flush occurs.
I already search on google, but I couldn't find the answer.
This is my Entity classes.
#Entity
#Table(name = "users")
#EntityListeners(AuditingEntityListener.class)
public class User {
private long serial;
private String username;
private String password;
public User() {
}
public User(long serial, String username, String password) {
setSerial(serial);
setUsername(username);
setPassword(password);
}
#Column(name = "serial", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
public long getSerial() {
return serial;
}
public void setSerial(long serial) {
this.serial = serial;
}
#Id
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "password", nullable = false)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "serial: " + this.serial + ", username: " + this.username + ", password: " + this.password;
}
}
Entity
#Table(name = "sites")
#EntityListeners(AuditingEntityListener.class)
#IdClass(Site.class)
public class Site implements Serializable{
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private String username;
private String siteURL;
#Id
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Id
public String getSiteURL() {
return siteURL;
}
public void setSiteURL(String siteName) {
this.siteURL = siteName;
}
}
And this is class had problem.
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private SiteRepository siteRepository;
private CryptoUtil passwordEncoder = new CryptoUtil();
...
#PutMapping("/users/{username}")
public User updateUser(#PathVariable(value = "username") String username, #Valid #RequestBody User userDetails)
throws ResourceNotFoundException {
User user = userRepository.findById(username)
.orElseThrow(() -> new ResourceNotFoundException("User not found on :: " + username));
List<Site> sites = siteRepository.findByUsername(user.getUsername());
userDetails.setPassword(passwordEncoder.encryptSHA256(userDetails.getPassword()));
final User updateUser = userRepository.save(userDetails);
for (Site site : sites)
{
site.setUsername(userDetails.getUsername());
site = siteRepository.save(site);
}
userRepository.delete(user);
return updateUser;
}
....
}
The for-each statement occurs error.
PLEASE HELP ME
Why did you do this?
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private String username;
It should be:
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private User user;
I'll also suggest you to use the primary key as foreign key.
And you can't have multiple #Id in an entity.

SpringBoot Rest response not deserialiazed with jackson

I am a running a project with SpringBoot. In this project I am calling an external Rest Service. I have modeled the response items into bean.
But when I get the response back the data are not serialised into the beans.
I guess there must be some configuration missing but I cannot find what.
I have added onfiguration spring-boot-starter-test to the configuration of Maven:
The rest client:
#RunWith(SpringRunner.class)
#SpringBootTest
public class RestClientTest {
#Autowired
private RestTemplateBuilder restTemplate;
#Test
public void sayHello() {
System.out.println("Hello");
assert(true);
}
#Test
public void testGetEmployee() {
RestTemplate template = restTemplate.build();;
HttpHeaders headers = new HttpHeaders();
List<MediaType> types = new ArrayList<MediaType>();
types.add(MediaType.APPLICATION_JSON);
types.add(MediaType.APPLICATION_XML);
headers.setAccept(types);
headers.set("Authorization", "Bearer gWRdGO7sUhAXHXBnjlBCtTP");
HttpEntity<Items> entity = new HttpEntity<Items>(headers);
String uri = "https://mytest.com/employees";
//ResponseEntity<String> rec = template.exchange(uri, HttpMethod.GET, entity, String.class);
//System.out.println("Received: " + rec);
ResponseEntity<Items> rec = template.exchange(uri, HttpMethod.GET, entity, Items.class);
System.out.println("Received: " + rec);
}
}
When I inspect the elements of the response it, I get a list, all the items are with null values
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public class Item implements Serializable {
#JsonProperty
private String id;
#JsonProperty
private String name;
#JsonProperty
private String email;
#JsonProperty
private String phone;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public class Items implements Serializable {
#JsonProperty
private List<Item> items = new ArrayList<Item>();
public List<Item> getItems() {
return items;
}
}
Do you see what I am missing here?
The response is like this:
{
"items": [
{
"item": {
"id": 0,
"name": "string",
"email": "string",
"phone": "string",
Do you see what I am missing here?
Thanks
Gilles
The way you have implemented will try to deserialize data into Items class. But it doesn't have the required properties to deserialize. When you need to get a list of data through rest template exchange, you can get them as follows.
Get data as an array and convert it into arrayList.
Item[] itemArray = template.exchange(uri, HttpMethod.GET, entity, Item[].class).getBody();
List<Item> itemList = Arrays,asList(itemArray);
or
Use ParameterizedTypeReference to get data as a list
ResponseEntity<List<Item>> itemList = template.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<List<Item>>() {});
List<Item> itemList = template.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<List<Item>>() {}).getBody(); // access list directly
You might need to add this to your ObjectMapper:
mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);
And on your entity add #JsonRootName("item")

Why JPA Hibernate changes the name of the field?

In model class I have isActive field with is boolean, that represent the is_active field in MySql DB. Here is whole model class:
package ca.gatin.model;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "Account")
public class Account {
#Id
#GeneratedValue
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(nullable = false, unique = true)
private String email;
#Column(nullable = false)
private String password;
#Column(name = "is_active", nullable = false)
private boolean isActive;
#Column(name = "date_created")
private Date dateCreated;
#Column(name = "date_last_modified")
private Date dateLastModified;
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
public Date getDateLastModified() {
return dateLastModified;
}
public void setDateLastModified(Date dateLastModified) {
this.dateLastModified = dateLastModified;
}
}
But when I fetch account let's say through REST API like:
#RequestMapping(
value = "/{id}",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ServiceResponse<Account> getAll(#PathVariable("id") Long id) {
ServiceResponse<Account> serviceResponse = accountService.getAccountById(id);
return serviceResponse;
}
In a reply object I get isActive field renamed by Hibernate to "active" like this:
{
"id": 19,
"firstName": "Julia",
"lastName": "Sarandi",
"email": "julia#gatin.ca",
"password": "111111",
"dateCreated": 1451293826000,
"dateLastModified": null,
"active": true
}
Why? Why all other field's names stay same as in Account class, but isActive is renamed?
That is one question, and another question is:
I am new in Hibernate, and I do no understand why in logs of Hibernate DB requests is shows some weird queries:
Hibernate: select account0_.id as id1_0_0_, account0_.date_created as date_cre2_0_0_, account0_.date_last_modified as date_las3_0_0_, account0_.email as email4_0_0_, account0_.first_name as first_na5_0_0_, account0_.is_active as is_activ6_0_0_, account0_.last_name as last_nam7_0_0_, account0_.password as password8_0_0_ from Account account0_ where account0_.id=?
What query language is it? What are symbols: "0_", "0_0_". Can I switch logs to show MySQL queries to make it more understandable?
FYI
In my application.properties file I have following configuration:
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.naming-strategy=org.hibernate.cfg.DefaultNamingStrategy
Change getter and setter method name for isActive field as:
public boolean getIsActive() {
return isActive;
}
public void setIsActive(boolean isActive) {
this.isActive = isActive;
}
Then it return isActive in response.
That has nothing to do with Hibernate, and everything to do with your JSON marshaller. Spring uses Jackson, and Jackson uses bean properties (i.e. getters) to access the data and transform them to JSON fields. Your getter is named isActive(), and thus corresponds to a bean property named active, hence the name of the attribute in the JSON.
If you want the JSON field to be named isActive, then your getter should be isIsActive(). Or much better, you should annotate it with #JsonProperty("isActive").
To answer your second question, the query is a SQL query, generated by Hibernate. It changes the name of tables and assigns aliases to columns mainly to disambiguate tables, and fields of different tables that could have the same name, AFAIK.

Dropwizard Hibernate Configuration

I am new to Dropwizard and so far everything was going well till I started messing with Hibernate and MySQL. My problem is: Hibernate won't create tables and consequently no columns in my DB.
The only warning I get when running my jar file is:
org.hibernate.cfg.environment hibernate.properties not found
But do I need it at all? As I am having all configuration and mapping already.
Here is my application class:
public class LibraryApplication extends Application<LibraryConfiguration> {
public static void main(String[] args) throws Exception {
new LibraryApplication().run(args);
}
#Override
public String getName() {
return "hello backend";
}
private final HibernateBundle<LibraryConfiguration> hibernate = new HibernateBundle<LibraryConfiguration>(Book.class){ //more entities can be added separated with a coma
public DataSourceFactory getDataSourceFactory(LibraryConfiguration configuration) {
return configuration.getDataSourceFactory();
}
};
#Override
public void initialize(Bootstrap<LibraryConfiguration> bootstrap) {
bootstrap.addBundle(new AssetsBundle("/webapp", "/", "index.html", "static"));
bootstrap.addBundle(hibernate);
}
#Override
public void run(LibraryConfiguration configuration,
Environment environment) {
final BookDAO dao = new BookDAO(hibernate.getSessionFactory());
final TestResource resource = new TestResource(
configuration.getTemplate(), configuration.getDefaultName());
final TemplateHealthCheck healthCheck = new TemplateHealthCheck(
configuration.getTemplate());
environment.healthChecks().register("template", healthCheck); //register the health check
environment.jersey().register(resource); //register the resource class
environment.jersey().register(new BookResource(dao));
}
}
YAML file:
server:
type: simple
rootPath: '/api/*'
applicationContextPath: /
connector:
type: http
port: 8080
template: Hello, %s!
defaultName: back-end
database:
# the name of your JDBC driver
driverClass: com.mysql.jdbc.Driver
# the JDBC URL
url: jdbc:mysql://localhost:3306/books
# the username
user: root
# the password
password: root
# any properties specific to your JDBC driver:
properties:
charSet: UTF-8
hibernate.dialect: org.hibernate.dialect.MySQLDialect #org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.hbm2ddl.auto: create
Configurtion class:
public class LibraryConfiguration extends Configuration{
#Valid
#NotNull
#JsonProperty
private DataSourceFactory database = new DataSourceFactory();
#JsonProperty("database")
public DataSourceFactory getDataSourceFactory() {
return database;
}
#NotEmpty
private String template;
#NotEmpty
private String defaultName = "";
#JsonProperty
public String getTemplate() {
return template;
}
#JsonProperty
public void setTemplate(String template) {
this.template = template;
}
#JsonProperty
public String getDefaultName() {
return defaultName;
}
#JsonProperty
public void setDefaultName(String name) {
this.defaultName = name;
}
}
and my entity:
#Entity
#Table(name = "book")
#NamedQueries({
#NamedQuery(
name = "library.core.Book.findAll",
query = "SELECT b FROM book b"
)
})
public class Book{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column
private Long id;
#Column(name = "title")
#NotNull
private String title;
#Column(name = "author")
#NotNull
private String author;
#Column(name = "date")
private long date;
#Column(name = "description")
private String description;
#Column(name = "image")
private String image;
public Book(String title, String author){
this.title = title;
this.author = author;
}
#JsonProperty
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
#JsonProperty
public Long getId() {
return id;
}
#JsonProperty
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
#JsonProperty
public long getDate() {
return date;
}
public void setDate(long date) {
this.date = date;
}
#JsonProperty
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#JsonProperty
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public void setId(Long id) {
this.id = id;
}
}
I have already been to many tutorials but none of them really explains how to configure hibernate. Thank you in advance.
I have finally solved this problem, which was not a big deal actually. Just a small mistake as it was expected.
My problem was a Book class, IDE automatically imported the java library called Book in the LibraryApplication class, so DB was not mapping it.
On the other hand, in the Book class the named query should be as follows:
#NamedQuery(
name = "library.core.Book.findAll",
query = "SELECT b FROM Book b"
)
My mistake: I was writing Book with small letter.