How to add a calculated field to an entity?
DB table
table person {
id number,
first_name varchar,
last_name varchar,
...
}
Java entity
public class person {
BigDecimal id;
String firstName;
String lastName;
...//getters and setters
//what to add here???
public String getFullName() {
return firstName + " " + lastname;
}
}
I tried adding #Transient, but the field is ignored when converting to json.
I tried just leaving the method there, throws an exception that the setter is missing. adding the setter throws another exception that the field does not exist in the DB.
I tried adding #Transient and #JsonPropert, but the field is ignored when converting to json.
I tried adding #Formula, but hibernate (i think) says it is not implemented.
The idea is to have a simple calculated field that is ignored by jpa/hibernate but is used by jackson.
How can I accomplish this?
EDIT
Example full class
#Entity
#Table(name="FDF_PATIENT_COUNTIE")
#JsonIdentityInfo(generator = JSOGGenerator.class)
#JsonIgnoreProperties(ignoreUnknown = true)
#Audited
public class PatientCounty extends FgaBaseClass {
private static final long serialVersionUID = 1425318521043179798L;
private BigDecimal id;
private County FCounties;
private Patient patients;
public PatientCounty() {
}
public PatientCounty(County FCounties, Patient patients) {
this.FCounties = FCounties;
this.patients = patients;
}
#SequenceGenerator(name="generator", sequenceName="FDF_PATIENT_COUNTIE_SEQ")
#Id
#GeneratedValue(strategy=SEQUENCE, generator="generator")
#Column(name="ID", unique=true, nullable=false, precision=22, scale=0)
public BigDecimal getId() {
return this.id;
}
public void setId(BigDecimal id) {
this.id = id;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_F_COUNTIE")
public County getFCounties() {
return this.FCounties;
}
public void setFCounties(County FCounties) {
this.FCounties = FCounties;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_FDF_PATIENT")
public Patient getPatients() {
return this.patients;
}
public void setPatients(Patient patients) {
this.patients = patients;
}
}
Related
On the client app I have this POJO
public class Chicken {
private String name;
private int age;
public Chicken(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
and I run this
RestClient get = RestClient.create().method("GET")
.host("http://localhost:8080/DevCrowd")
.path("resources/chickens");
GluonObservableList<Chicken> sample = DataProvider.retrieveList(
get.createListDataReader(Chicken.class));
System.out.println(sample);
But I get the error:
WARNING: Failed to create object of type class com.devcrowd.test.Chicken from the following json object {"id":1,"name":"AAA","age":12}
java.lang.InstantiationException: com.gluonhq.notesapp.Chicken
at java.lang.Class.newInstance(Class.java:427)
at com.gluonhq.connect.converter.JsonConverter.readFromJson(JsonConverter.java:111)
at com.gluonhq.connect.converter.JsonIterableInputConverter.next(JsonIterableInputConverter.java:108)
at com.gluonhq.connect.provider.DataProvider.lambda$retrieveList$21(DataProvider.java:194)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NoSuchMethodException: com.gluonhq.notesapp.Chicken.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 6 more
On the server I have this entity:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
#Entity
#NamedQuery(name="all", query = "SELECT c FROM Chicken C")
public class Chicken {
#Id
#GeneratedValue
private long id;
private String name;
private int age;
public Chicken() {}
public Chicken(String name, int age) {
this.name = name;
this.age = age;
}
}
and this service:
#Path("chickens")
public class ChickensResource {
#Inject
ChickenService cs;
#GET
#Produces(MediaType.APPLICATION_JSON)
public String chickens() {
JsonArrayBuilder jsonArrayBuilder = Json.createArrayBuilder();
List<Chicken> chickens = cs.getAllChickens();
chickens.stream().map(chicken -> Json.createObjectBuilder()
.add("name", chicken.getName())
.add("age", chicken.getAge())
.build())
.forEach(jsonArrayBuilder::add);
return jsonArrayBuilder.build().toString();
}
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public void save(JsonObject chicken) {
String name = chicken.getString("name");
int age = chicken.getInt("age");
cs.save(new Chicken(name, age));
}
}
I can POST correctly (I check the DB and what I POST is there so this is why the error stack has a Chicken object ready) but I can't read it back. Why is that?
As you can read in the docs for JsonConverter::readFromJson:
Convert the provided JSON Object into a Java object. If a new instance could not be created from the specified targetClass in the constructor, then null will be returned.
The conversion works by inspecting all the property methods of the target class. A property method is any field that has both a getter and a setter method.
Now if you check your exception:
java.lang.InstantiationException: com.gluonhq.notesapp.Chicken
at java.lang.Class.newInstance(Class.java:427)
the reason becomes clear: the target class (com.gluonhq.notesapp.Chicken) can't be instantiated, because it looks for a parameterless constructor.
All you'll need to do is add one:
public class Chicken {
private String name;
private int age;
public Chicken() { }
public Chicken(String name, int age) {
this.name = name;
this.age = age;
}
...
}
EDIT
The DataProvider returns an observable list, and you should use the initializedProperty() to find out when the list is ready, so you can get its content:
RestClient get = RestClient.create().method("GET")
.host("http://localhost:8080/DevCrowd")
.path("/resources/chickens");
GluonObservableList<Chicken> sample = DataProvider.retrieveList(
get.createListDataReader(Chicken.class));
sample.initializedProperty().addListener((obs, ov, nv) -> {
if (nv) {
for (Chicken chicken : sample) {
System.out.println(chicken);
}
}
});
Java class (used as a Data Transfer Object):
class Resource also has a field named id with a different type along with its getter and setter, hence the syntax error.
class A extends Resource
{
private int id;
public int getId() { return id; } // syntax error as getId() function already exists in Resource
public void setId(int id) { this.id = id; }
}
Since the above class is a DTO, a JSON response (with field id) will be mapped to it, and getId() cannot be used, I want to change the field to _id_ and change getter and setter correspondingly, and mark it with an annotation saying bind this to id field.
Note: I'm using spring boot. I tried using #JsonProperty annotation but that didn't work. Is there an annotation for doing this in spring?
Googled and found this question: Jackson serialization: how to ignore superclass properties. Adapted it for your problem.
public class A extends B {
private int id;
public A(int id) {
super.setId("id" + id);
this.id = id;
}
#Override
#JsonProperty("_id_")
public String getId() {
return super.getId();
}
#Override
#JsonProperty("_id_")
public void setId(String id) {
super.setId(id);
}
#JsonProperty("id")
public int getIntId() {
return id;
}
#JsonProperty("id")
public void setIntId(int id) {
this.id = id;
}
}
public class B {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
Tested it with this:
#RestController
public class TestController {
#GetMapping("/test")
public A test() {
return new A(1);
}
}
And the output was:
{
"_id_": "id1",
"id": 1
}
public A extends Resource {
private int id;
#JsonProperty("_id")
public int getId() {
return id;
}
#JsonProperty("id")
public void setId(int id) {
this.id = id;
}
}
the method names should be different, so jackson parses it as different fields, not as one field.
I have a CrudRepository with two entities.
Problem: The characteristic entity always creates an additional id field in the database but does not select the correct characteristic_id field to generate JSON.
machine entity
machine_id || name || description
characteristic entity
characteristic_id || machine_id || name || description || type || value
Question: Why does the characteristic entity always create an id field named id and does not use the definded filed characteristic_id?
JSON
How the entity manipulates the database layout:
Machine entity
#Entity
#Table(name = "maschine")
public class Machine {
private int machine_id;
private String name;
private String description;
private Set<Characteristic> characteristics;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "machine", cascade = CascadeType.ALL)
public Set<Characteristic> getCharacteristics() {
return characteristics;
}
public void setCharacteristics(Set<Characteristic> characteristics){
this.characteristics = characteristics;
}
public Machine(){}
public Machine(String name, String description){
this.name = name;
this.description = description;
}
#Override
public String toString() {
return "Machine [id=" + machine_id + ", name=" + name + ", description=" + description + "]";
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
public int getId() {
return machine_id;
}
public void setId(int machine_id) {
this.machine_id = machine_id;
}
#Column(name="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Column(name="description")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
Characteristic entity
#Entity
#Table(name = "characteristic")
public class Characteristic {
private int characteristic_id;
private String name;
private String description;
private int type;
private int value;
private Machine machine;
#ManyToOne
#JoinColumn(name="machine_id")
public Machine getMachine(){
return machine;
}
public void setMachine(Machine machine){
this.machine = machine;
}
public Characteristic() {}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
public int getCharacteristic_Id() {
return characteristic_id;
}
public void setCharacteristic_Id(int characteristic_id) {
this.characteristic_id = characteristic_id;
}
#Column(name="name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Column(name="description")
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#Column(name="type")
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
#Column(name="value")
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
#Override
public String toString() {
return "Characteristic [id=" + characteristic_id + ", name=" + name + ", description=" + description + ", type=" + type
+ ", value=" + value + "]";
}
}
My CrudRepository to interact with the database:
public interface MachineRepository extends CrudRepository<Machine, Integer>{
}
My Controller:
#RestController
public class HomeController {
private final MachineRepository machineRepository;
#Autowired
HomeController(MachineRepository machineRepository) {
this.machineRepository = machineRepository;
}
#RequestMapping(value = "/machine", method = RequestMethod.GET)
Collection<Machine> readMachines(){
return (Collection<Machine>) machineRepository.findAll();
}
}
This is all code I have written for the database operations..
Change to this code
#Id
#GeneratedValue
#Column(name="id", nullable = false)
public int getCharacteristic_id() {
return characteristic_id;
}
By default as you have not specified the column name in your configuration hibernate will use the name of the getter methode without "get" part as the column name in the table. Below is the configuration in your Characteristic Entity
#Id
#GeneratedValue
public int getId() {
return characteristic_id;
}
As you have not specified any explicit column name for the memeber characteristic_id , hibernate by default chooses Id (removing get from the getId() method) as the column name for the member. And hence it always creates a column named Id for you characteristics_id member .
In order to force hibernate to use characteristic_id as the column name , you can use #Column annotation and provide the column name as characteristics_id
apart from the #Id and #GeneratedValue you are using to mark the getter.
#Id
#GeneratedValue
#Column(name="characteristic_id")
public int getId() {
return characteristic_id;
}
or you can change the name of getter and setter as below
#Id
#GeneratedValue
public int getCharacterisitc_id() {
return characteristic_id;
}
public void setCharacterisitc_id(int characteristic_id) {
this.characteristic_id = characteristic_id;
}
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.
I am trying to make a simple round-trip with a REST API that leads to storing an entity into the db and then returns the stored entity.
Going down works fine and the entity is stored and correctly returned to the REST Controller. However, when I return it, Jackson seems to serialize it incorrectly, as the "name" attribute is not included.
This is the entity:
#Entity
#Configurable
public class MyEntity extends IdentifiableEntity {
private String name;
protected MyEntity() {
};
public MyEntity(String name) {
this.name = name;
}
}
and the extended entity:
#Configurable
#Inheritance(strategy = InheritanceType.JOINED)
#Entity
public abstract class IdentifiableEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version = 1;
public String toString() {
return ReflectionToStringBuilder.toString(this,
ToStringStyle.SHORT_PREFIX_STYLE);
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getVersion() {
return this.version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
The REST controller is:
#RestController
#RequestMapping("/service")
public class Service {
#RequestMapping(value = "/public/{name}", method = RequestMethod.GET)
public MyEntity storeEntityPublic(#PathVariable String name) {
System.out.println("Hello " + name
+ ", I am saving on the db. (PUBLIC)");
MyEntity saved = controller.saveEntity(name);
return saved;
}
}
Then my business logic:
#Service
public class LogicController {
#Autowired
private MyEntityRepository myEntityRepository;
public MyEntity saveEntity(String name) {
MyEntity cg = new MyEntity(name);
return myEntityRepository.save(cg);
}
}
I am using Spring repositories:
#Repository
public interface MyEntityRepository extends JpaSpecificationExecutor<MyEntity>,
JpaRepository<MyEntity, Long> {
}
The returned JSON is:
{"id":12,"version":1}
Where is my "name" attribute? Is is set in the variable being returned by the REST controller.
I found the trick: MyEntity needs to have a public get for the property that has to be shown. A good reason to use a DTO pattern.
In response to your "I don't want to have my Entity "dirty"" comment: Jackson allows the use of so-called Mixins. They allow you to define annotations for your class outside the class itself. In your case it could look like this:
public abstract class MyEntityMixin {
#JsonProperty
private String name;
}
You may keep it as a field and annotate the field with #JsonProperty if you like.