I have the next maven projects:
project-model : I have JPA entities
project-rest : Spring data, spring rest based on spring boot
project-client : Jersey clients to consume the rest services
project-web : Only jsf web application
project-desktop : Java Fx desktop application
project-android : Mobile application which consumes my Rest web services.
I'm planing to remove the JPA entities from the project-model and place there only DTO's pojos and interfaces and place my JPA entities in the rest project in order to remove the jpa dependencies from the project-model. This is because I don't want to have JPA dependencies in the project-android, project-web and project-desktop.
I was thinking to follow the next schema:
#JsonSerialize(as=CountryDto.class)
#JsonDeserialize(as=CountryDto.class)
public interface ICountry extends Serializable
{}
#Entity
#Table(name = "COUNTRY")
#JsonSerialize(as=Country.class)
#JsonDeserialize(as=Country.class)
public class Country implements ICountry
{}
public class CountryDto implements ICountry
{}
And if I need to convert from Entities to DTO's use mapstruct or Selma.
But I'm not sure if this is the best practice because I have problems in my code like the next:
#JsonSerialize(as=CityDto.class)
#JsonDeserialize(as=CityDto.class)
public interface ICity extends Serializable
{
public Integer getIdCity();
public void setIdCity(Integer idCity);
public String getName();
public void setName(String name);
public ICountry getCountryId();
public void setCountryId(ICountry countryId);
}
public class CityDto implements ICity
{
private static final long serialVersionUID = -6960160473351421716L;
private Integer idCity;
private String name;
private CountryDto countryId;
public CityDto()
{
// TODO Auto-generated constructor stub
}
public CityDto(Integer idCity, String name, CountryDto countryId)
{
super();
this.idCity = idCity;
this.name = name;
this.countryId = countryId;
}
public CityDto(Integer idCity, String name)
{
super();
this.idCity = idCity;
this.name = name;
}
#Override
public Integer getIdCity()
{
return idCity;
}
#Override
public void setIdCity(Integer idCity)
{
this.idCity = idCity;
}
#Override
public String getName()
{
return name;
}
#Override
public void setName(String name)
{
this.name = name;
}
#Override
public ICountry getCountryId()
{
return countryId;
}
#Override
public void setCountryId(ICountry countryId)
{
this.countryId = (CountryDto) countryId;
}
}
#Entity
#Table(name = "CITY")
#JsonSerialize(as=City.class)
#JsonDeserialize(as=City.class)
public class City implements ICity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID_CITY")
private Integer idCity;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 100)
#Column(name = "NAME")
private String name;
#JoinColumn(name = "COUNTRY_ID", referencedColumnName = "ID_COUNTRY")
#ManyToOne(optional = false)
private Country countryId;
private static final long serialVersionUID = 1L;
public City()
{
}
public City(Integer idCity)
{
this.idCity = idCity;
}
public City(Integer idCity, String name)
{
this.idCity = idCity;
this.name = name;
}
#Override
public Integer getIdCity()
{
return idCity;
}
#Override
public void setIdCity(Integer idCity)
{
this.idCity = idCity;
}
#Override
public String getName()
{
return name;
}
#Override
public void setName(String name)
{
this.name = name;
}
#Override
public ICountry getCountryId()
{
return countryId;
}
#Override
public void setCountryId(ICountry countryId)
{
this.countryId = (Country) countryId;
}
#Override
public int hashCode()
{
int hash = 0;
hash += (idCity != null ? idCity.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object)
{
// TODO: Warning - this method won't work in the case the id fields are
// not set
if (!(object instanceof City))
{
return false;
}
City other = (City) object;
if ((this.idCity == null && other.idCity != null) || (this.idCity != null && !this.idCity.equals(other.idCity)))
{
return false;
}
return true;
}
#Override
public String toString()
{
return "com.neology.ebreeder.model.entities.City[ idCity=" + idCity + " ]";
}
}
And as You can see in the entity I have getters and setters using the shared interface, and I think that It could provoke problems, I thought to override the getters using the entity but I can't override the setters.
I cant do this:
#Override
public Country getCountryId()
{
return countryId;
}
But I can't do this :
#Override
public void setCountryId(Country countryId)
{
this.countryId = (Country) countryId;
}
Do you see a better solution or could you give me your point of view :)
thanks
Based on past experience, I do not think it is a good idea to use an interface that is shared between the DTO model and the JPA model.
You are in essence tightly coupling your DTO model to your JPA model with this approach.
I would rather have them loosely coupled and use a separate framework to copy between these two models. This will need to be powered by a meta model (could be derived from JPA) to walk and copy the data from one model to another based on the getters and setters.
Related
In my project I try yo use Spring data Jpa. My find methods(findById, findAll) works correctly, but delete and save method works with problems. Delete method delete only from duck table. Save doesn't work:
Exception in thread "main" org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find springdata.entities.FrogJpa with id 2; nested exception is javax.persistence.EntityNotFoundException: Unable to find springdata.entities.FrogJpa with id 2
I have 2 entities: Frog and Duck. Every ducks have 1 Frog(OneToOne). There are problems with entities relationship?
There are my entities class:
#Entity
#Table(name = "DUCKS")
public class DuckJpa implements Serializable {
#Id
private int id;
#Column(name = "NAME")
private String name;
#Column(name = "FLY")
private String flyBehavior;
#Column(name = "QUACK")
private String quackBehavior;
#OneToOne(optional = false)
#JoinColumn(name = "FROG_ID", unique = true, nullable = false, updatable = false)
private FrogJpa frogJpa;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setFlyBehavior(String flyBehavior) {
this.flyBehavior = flyBehavior;
}
public void setQuackBehavior(String quackBehavior) {
this.quackBehavior = quackBehavior;
}
public void setFrogJpa(FrogJpa frogJpa) {
this.frogJpa = frogJpa;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getFlyBehavior() {
return flyBehavior;
}
public String getQuackBehavior() {
return quackBehavior;
}
public FrogJpa getFrogJpa() {
return frogJpa;
}
And Frog:
#Entity
#Table(name = "FROGS")
public class FrogJpa {
#OneToOne(optional = false, mappedBy = "frogJpa")
private DuckJpa duckJpa;
#Id
private int id;
#Column(name = "name")
private String name;
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setDuckJpa(DuckJpa duckJpa) {
this.duckJpa = duckJpa;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public DuckJpa getDuckJpa() {
return duckJpa;
}
}
My service class:
public interface DuckService {
List<DuckJpa> findAll();
Optional<DuckJpa> findById(Integer i);
DuckJpa save(DuckJpa duckJpa);
void delete(DuckJpa duckJpa);
}
And it's implementation:
#Service("springJpaDuckService")
#Transactional
public class DuckServiceImpl implements DuckService {
#Autowired
private DuckJpaRepository duckJpaRepository;
#Transactional(readOnly = true)
public List<DuckJpa> findAll() {
return new ArrayList<>(duckJpaRepository.findAll());
}
#Override
public Optional<DuckJpa> findById(Integer i) {
return duckJpaRepository.findById(i);
}
#Override
public DuckJpa save(DuckJpa duckJpa) {
duckJpaRepository.save(duckJpa);
return duckJpa;
}
#Override
public void delete(DuckJpa duckJpa) {
duckJpaRepository.delete(duckJpa);
}
Use #OneToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY).
For more information please refer What is cascading in Hibernate?
I have built a REST API using Spring Boot Data REST. I'm using an embeddedId and have also implemented a BackendIdConverter.
Below is my Embeddable class
#Embeddable
public class EmployeeIdentity implements Serializable {
#NotNull
#Size(max = 20)
private String employeeId;
#NotNull
#Size(max = 20)
private String companyId;
public EmployeeIdentity() {}
public EmployeeIdentity(String employeeId, String companyId) {
this.employeeId = employeeId;
this.companyId = companyId;
}
public String getEmployeeId() {
return employeeId;
}
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
public String getCompanyId() {
return companyId;
}
public void setCompanyId(String companyId) {
this.companyId = companyId;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmployeeIdentity that = (EmployeeIdentity) o;
if (!employeeId.equals(that.employeeId)) return false;
return companyId.equals(that.companyId);
}
#Override
public int hashCode() {
int result = employeeId.hashCode();
result = 31 * result + companyId.hashCode();
return result;
}
}
Here's my Employee model
#Entity
#Table(name = "employees")
public class Employee {
#EmbeddedId
private EmployeeIdentity id;
#NotNull
#Size(max = 60)
private String name;
#NaturalId
#NotNull
#Email
#Size(max = 60)
private String email;
#Size(max = 15)
#Column(name = "phone_number", unique = true)
private String phoneNumber;
public Employee() {}
public Employee(EmployeeIdentity id, String name, String email, String phoneNumber) {
this.id = id;
this.name = name;
this.email = email;
this.phoneNumber = phoneNumber;
}
public EmployeeIdentity getId() {
return id;
}
public void setId(EmployeeIdentity 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 getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
And to have resource links generated properly using my embedded id instead of a qualified class name
#Component
public class EmployeeIdentityIdConverter implements BackendIdConverter {
#Override
public Serializable fromRequestId(String id, Class<?> aClass) {
String[] parts = id.split("_");
return new EmployeeIdentity(parts[0], parts[1]);
}
#Override
public String toRequestId(Serializable source, Class<?> aClass) {
EmployeeIdentity id = (EmployeeIdentity) source;
return String.format("%s_%s", id.getEmployeeId(), id.getCompanyId());
}
#Override
public boolean supports(Class<?> type) {
return Employee.class.equals(type);
}
}
And here's my repository code
#RepositoryRestResource(collectionResourceRel = "employees", path = "employees")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, EmployeeIdentity> {
}
This works fine with GET requests but I need to be able to POST. The first thing I noticed that when I do a POST with the json
{
"id": {
"employeeId": "E-267",
"companyId": "D-432"
},
"name": "Spider Man",
"email": "spman#somedomain.com",
"phoneNumber": "+91-476253455"
}
This doesn't work. EmployeeIdentityIdConverter#fromRequestId throws a null pointer exception because the string parameter is null. So I added a null check and return default EmployeeIdentity when id is null. As described by this answer https://stackoverflow.com/a/41061029/4801462
Modified EmployeeIdentityIdConverter#fromRequestId
#Override
public Serializable fromRequestId(String id, Class<?> aClass) {
if (id == null) {
return new EmployeeIdentity();
}
String[] parts = id.split("_");
return new EmployeeIdentity(parts[0], parts[1]);
}
But this raised another problem. My implementations for hashCode and equals now through null pointer exceptions since the default constructor was used and the employeeId and companyId are null.
In an attempt to fix this, I gave default values to employeeId and companyId
**Modified Employee#Employee() constructor*
public Employee() {
this.employeeId = "";
this.companyId = "";
}
NOTE
I am not even sure of what I was doing above. I was just trying to fix the small problems as they occurred.
By the way if you guessed this didn't work then you're right. While I didn't get an error and the request was successful, I didn't get the behavior I expected. A new entry was created with empty employeeId and companyId.
How do make POST to REST API whose model uses #EmbeddedId with spring boot data rest?
Here is an other solution. (Still not perfect though.)
Expose the id for your Employee class:
#Configuration
protected class MyRepositoryRestConfigurer implements RepositoryRestConfigurer {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(ThemeMessage.class);
}
}
Add the following line to your converter (during POST requests the id will be null):
#Override
public Serializable fromRequestId(String id, Class<?> aClass) {
if(id==null) {
return null;
}
String[] parts = id.split("_");
return new EmployeeIdentity(parts[0], parts[1]);
}
The following POST request then will work:
{
"id": {
"employeeId": "E-267",
"companyId": "D-432"
},
"name": "Spider Man",
"email": "spman#somedomain.com",
"phoneNumber": "+91-476253455"
}
However, the id field will be exposed in all of the responses. But maybe it's not a real problem, because when you use a composite id it usually means that the id is not only an abstract identifier, but its parts have meaningful content which should appear in the entity body.
Actually, I'm thinking of adding these lines to my own code too .... :)
I had a similar problem and I couldn't find a solution for creating new entities via the POST /entities endpoint.
However, you can also create a new entity via PUT /entities/{newId} endpoint. And the converter works fine for these endpoints.
I also completely denied the POST endpoint avoiding the 500 responses:
#PostMapping(value = "/themeMessages")
public ResponseEntity<Void> postThemeMessage() {
return new ResponseEntity<>(HttpStatus.METHOD_NOT_ALLOWED);
}
I'm using JPA and have some difficulties to understand how the One-To-Many Realtionship works.
I have the following two classes:
#Entity
public class myCheck {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
protected int Check_id;
#Column
private String name;
#ManyToOne
private mySystem system;
#Override
public String toString() {
return this.name;
}
public int getId() {
return Check_id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public mySystem getLinkSystem() {
return system;
}
public void linkSystem(mySystem system) {
this.system = system;
}
}
and:
#Entity
public class mySystem {
#Id
#GeneratedValue
#Column(name = "system_id")
protected int system_id;
#Column
public String name;
#ManyToOne(cascade=CascadeType.ALL)
private mySystem parent;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "system", fetch = FetchType.EAGER)
private List<myCheck> checks;
public mySystem() {
//subSystems = new ArrayList<mySystem>();
checks = new ArrayList<myCheck>();
}
public boolean linkCheck(myCheck hc) {
return checks.add(hc);
}
public boolean unlinkCheck(myCheck hc) {
return checks.remove(hc);
}
public List<myCheck> getlinkedChecks() {
return checks;
}
public myCheck getLinkCheck(int hcId) {
for (myCheck hc : checks) {
if (hc.getId() == hcId)
return hc;
}
return null;
}
public int getId() {
return system_id;
}
public void setId(int id) {
this.system_id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return this.getName();
}
}
Now I have an existing System in my database, which is loaded:
// load System
EntityManager entityManager1 = entityManagerFactory.createEntityManager();
List<mySystem> systems = entityManager1.createQuery("from mySystem").getResultList();
entityManager1.close();
I want to add two new checks to Systems. What is working is:
EntityManager entityManager2 = entityManagerFactory.createEntityManager();
entityManager2.getTransaction().begin();
myCheck check = new myCheck();
check.setName("Check 1");
check.linkSystem(systems.get(0));
entityManager2.persist(check);
myCheck check2 = new myCheck();
check2.setName("Check 2");
check2.linkSystem(systems.get(0));
entityManager2.persist(check2);
entityManager2.merge(systems.get(0));
entityManager2.getTransaction().commit();
entityManager2.close();
But I can't do this:
EntityManager entityManager2 = entityManagerFactory.createEntityManager();
entityManager2.getTransaction().begin();
myCheck check = new myCheck();
check.setName("Check 1");
systems.get(0).linkCheck(check);
entityManager2.persist(check);
myCheck check2 = new myCheck();
check2.setName("Check 2");
systems.get(0).linkCheck(check);
entityManager2.persist(check2);
entityManager2.merge(systems.get(0));
entityManager2.getTransaction().commit();
entityManager2.close();
The second solution will save the checks, but I don't link them with the system.
Has someone a explanation for this? I really want to understand this.
You have bidirectional relation which means each side of the relation should have a reference to the other side.
so in your persistence logic you will also need to inject the system in your check
myCheck check = new myCheck();
check.setName("Check 1");
check.linkSystem(systems.get(0);
systems.get(0).linkCheck(check);
entityManager2.persist(check);
But in this case you will have a problem if your (systems.get(0)) is not attached to the persistence context because you will be having reference to deatached object when persisting the check, you can either put Cascade on the system inside check class or instead persist the system, it already cascades the check so the check will be persisted
I have such situation:
There are a few groups of POJO classes, each with one parent. Each group serves to be working with some recording of data - to the XML with the help of JAXB and to the database with the help of Hibernate. A simplified version of my working code is:
public static abstract class Habit{
String habitName;
/* constructors */
/* getter & setter */
}
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "habit")
public static class DBHabit extends Habit{
#Id
#GeneratedValue
private int id;
#Column(name = "habit_name")
public String getHabitName() {
return habitName;
}
public void setHabitName(String habitName) {
this.habitName = habitName;
}
}
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlRootElement(name = "habit")
public static class XmlHabit extends Habit{
#XmlElement
public String getHabitName() {
return habitName;
}
public void setHabitName(String habitName) {
this.habitName = habitName;
}
}
public static abstract class Person{
int age;
String name;
List<Habit> habits;
/* Constructor */
/* Getters & Setters */
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<Habit> getHabits() {
return habits;
}
public void setHabits(List<Habit> habits) {
this.habits = habits;
}
}
#Entity
#Access(AccessType.PROPERTY)
#Table(name = "person")
public static class DBPerson extends Person{
#Id
#GeneratedValue
private int id;
#Column(name = "age")
#Override
public int getAge() {
return age;
}
#Override
public void setAge(int age) {
this.age = age;
}
#Column(name = "name")
#Override
public String getName() {
return name;
}
#Override
public void setName(String name) {
this.name = name;
}
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = DBHabit.class)
#Override
public List<Habit> getHabits() {
return habits;
}
#Override
public void setHabits(List<Habit> friends) {
this.habits = habits;
}
}
#XmlAccessorType(XmlAccessType.PROPERTY)
#XmlType(name = "", propOrder = {
"age",
"name",
"habits"
})
#XmlRootElement(name = "state")
public static class XmlPerson extends Person{
#Override
#XmlElement(required = true)
public int getAge() {
return super.getAge();
}
#Override
public void setAge(int age) {
super.setAge(age);
}
#Override
#XmlElement(required = true)
public String getName() {
return super.getName();
}
#Override
public void setName(String name) {
super.setName(name);
}
#Override
#XmlElement(required = true)
#XmlElementWrapper(name = "friends")
public List<Habit> getHabits() {
return super.getHabits();
}
#Override
public void setHabits(List<Habit> habits) {
super.setHabits(habits);
}
}
I have problems with working with Hibernate. Thought I wrote that I expect the recording of DBHabit.class in targetEntity, hibernate does not record any information about habits, this table is always empty.
Please give me a hint, what can I make in such situation or advice, how I can make a similar abstract system like this for the writing in different ways.
Thank you for attention!
A couple of strange thing i noticed in your mapping:
a #OneToOne mapping, backed by a List<Habit>. Why not directly Habit ?
You annotate class with #Access(AccessType.PROPERTY) but then you put the mapping annotation on accessor methods (you're issue might come from there).
Instead you could try to annotate the parent abstract class as #MappedSuperclass. This way, you do not have to redefine the accessor. Or if you do not wish to put hibernate annotations in the parent class, remove the #Access(AccessType.PROPERTY) annotation.
I'm developing an application using java with hibernate 4.2.6 and spring 4.0.1. My application is a REST FULL application. for this I use jackson. My entities are as follow:
Calk.java:
#Entity
public class Calk {
#Id
#GeneratedValue
private Long id;
#OneToMany(mappedBy="calk", fetch=FetchType.LAZY)
List<BaseLayer> baseLayer = new ArrayList<BaseLayer>();
public void addBaseLayer(BaseLayer baseLayer){
this.baseLayer.add(baseLayer);
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#JsonIgnore
public List<BaseLayer> getBaseLayer() {
return baseLayer;
}
public void setBaseLayer(List<BaseLayer> baseLayer) {
this.baseLayer = baseLayer;
}
}
BaseLayer.java:
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="layer")
#JsonSubTypes(
{
#JsonSubTypes.Type(value=PointLayer.class, name="point")
})
#DiscriminatorValue("BaseLayerDiscriminator")
public class BaseLayer {
#Id
#GeneratedValue
protected Long gid;
#ManyToOne(fetch = FetchType.LAZY)
protected Calk calk;
public Long getGid(){
return gid;
}
public void setGid(Long gid){
this.gid = gid;
}
#JsonIgnore
public Calk getCalk(){
return calk;
}
public void setCalk(Calk calk){
this.calk = calk;
}
}
Now I have a class that extends from BaseLayer.java as follow:
PointLayer.java:
#Entity
#DiscriminatorValue("PointDiscriminator")
public class PointLayer extends BaseLayer{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now I create a json as follow and then send it to a controller:
{"layer": "point", "calk":{"id":1}, "name": "itsme"}
Now the controller defines as follow:
#RequestMapping("\test")
public String test(#RequestBody BaseLayer baseLayer){
System.out.println(baseLayer.getName());// this print "itsme"
Calk calk = baseLayer.getCalk();//it return null
if(calk == null)
return "its null";
else
return "its not null";
}
when we call the controller it return its not null. The calk should not be null.
Where is the problem?
Update:
When I remove #JsonIgnore at getCalk, It work fine. But Why? I want to ignore getCalk but NOT ignore setCalk.
#JsonIgnore in follow:
#JsonIgnore
public List<BaseLayer> getBaseLayer() {
return baseLayer;
}
set Ignore to following by default:
public void setBaseLayer(List<BaseLayer> baseLayer) {
this.baseLayer = baseLayer;
}
You must add #JsonProperty("baseLayer") On top of set function as follow:
#JsonProperty("baseLayer")
public void setBaseLayer(List<BaseLayer> baseLayer) {
this.baseLayer = baseLayer;
}