How to get subclasses attributes using SingleTableInheritance with SpringMVC - java

i am working on a web application using Spring, Hibernate and SpringMVC,
i am facing a problem with retreiving values from a subclass table using SingleTable inheritance strategy, here are my entities
Client.java (Super class)
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "typeClient", discriminatorType = DiscriminatorType.STRING)
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idClient;
private String matricule;
private String statut;
private String secteurDactivite;
private String nomClient;
private String emailClient;
private String numTelephone;
private String adresse;
//constructor
//getter & setters
}
Societe.java (subClass1)
#Entity
#DiscriminatorValue("Societe")
public class Societe extends Client implements Serializable{
private String nomResponsable;
private String emailResponsable;
private String telResponsable;
private String nomSuperieur;
private String emailSuperieur;
private String telSuperieur;
private String commentaire;
//constructeur sans parametre
public Societe() {
}
}
Particulier.java (subclass2)
#Entity
#DiscriminatorValue("Particulier")
public class Particulier extends Client implements Serializable {
private String cin;
//constructeur sans parametres
public Particulier() {
}
}
in my implementation i am using this methode to get a particular client with his ID
ClientDaoImpl.java
public class ClientDaoImpl implements ClientDao {
#PersistenceContext
private EntityManager em;
#Override
public Client getClientByID(int id_client) {
return em.find(Client.class, id_client);
}
When i ran this code i only selected the attributes of the superClass Client.
what i am trying to do is to get a client with its subclass whether it's a Societe or Particulier based on its type or clientID.
Please Help

As you don't know the type of client before querying and only it's ID, you will need to inspect the type and cast after you retrieve the record;
Client client1 = clientDao.getClientById(clientID);
if (client1 instanceof Societe) {
((Societe) client1).getCommentaire();
}
Depending on your use case, it may be useful to map the result of the client query to a ClientDescriptor object which contains all the fields for all client types and returns either nulls or blanks. This means you don't have to keep checking for client type everywhere;
public class ClientDTO {
//client fields
private String nomResponsable = "";
....
//subclass 1 fields.... initialize to empty
//subclass 2 fields .... initialize to empty
public ClientDTO (Client client) {
// set fields for client entity
}
public ClientDTO (Societe societe) {
this (societe);
// set societe fields.
}
// other constructors.
}

You can modify your getClientByID method to accept an additional argument which will say what type of entity your want to retrieve and get back:
public class ClientDaoImpl implements ClientDao {
#PersistenceContext
private EntityManager em;
public <T extends Client> T getByID(int id_client, Class<T> klass) {
return em.find(klass, id_client);
}
}
And you can use this dao in the following manner:
Societe societe = clientDao.getByID(42, Societe.class);
Particulier particulier = clientDao.getByID(43, Particulier.class);

Related

Java - JPA Repeatable #Embedded annotations

All tables in our shop have the same set of identical audit columns(system, user, terminal, etc)
So instead of copying the same columns in every class I tried to use the embedded annotation
#Entity
#Table(name="PRODUCT")
public class ProdEntity implements Serializable{
private long prod;
//set/get code
private CommonJrnFields jrn;
#Embedded
public CommonJrnFields getJrn() {return this.jrn;}
public void setJrn(CommonJrnFields jrn) {this.jrn = jrn;}
private OrdEntity ord;
#OneToOne(mappedBy="prod")
public OrdEntity getOrd() {return this.ord;}
public void setOrd(OrdEntity ord) {this.ord = ord;}
}
#Entity
#Table(name="ORDERS")
#NamedQuery(name="OrdEntity.findAll", query="SELECT o FROM OrdEntity o")
public class OrdEntity extends ProdEntity implements Serializable{
private long ord;
//set/get code
private CommonJrnFields jrn;
#Embedded
public CommonJrnFields getJrn() {return this.jrn;}
public void setJrn(CommonJrnFields jrn) {this.jrn = jrn;}
private ProdEntity prod;
#OneToOne(mappedBy="ord")
public ProdEntity getProd () {return this.prod;}
public void setProd(ProdEntity prod) {this.prod = prod;}
}
#Embeddable
public class CommonJrnFields {
private String user;
private String system;
//set/get code
}
As a result embedded columns of extended class overrides base class and named query returns
ProdEntity.prod
OrdEntity.ord
OrdEntity.system
OrdEntity.user
instead of
ProdEntity.prod
ProdEntity.system
ProdEntity.user
OrdEntity.ord
OrdEntity.system
OrdEntity.user
How to fix it?
Please do not offer the #AttributeOverrides solution.
I'm working with almost 100 tables and do attribute override in each class for 8 common columns specifying table="" does not make any sense. It would be much faster just to cut/paste the embeddable code into all entities if there is no other solutions.

Hibernate is not using the proxy of a session scope bean spring

I'm using Spring #Scope(value = "session", proxyMode=ScopedProxyMode.TARGET_CLASS) beans for objects that should be shared across a single Http-Session. This will provide for example one "Project" object for each User who is using my application.
To get this working I had to implement an interceptor for Hibernate that is returning the name of the class:
public class EntityProxySupportHibernateInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 7470168733867103334L;
#Override
public String getEntityName(Object object) {
return AopUtils.getTargetClass(object).getName();
}
}
With this interceptor I can use a Spring CrudRepository to save a Project-entity in the database:
#Repository
public interface ProjectRepository extends CrudRepository<Project, Integer> {
Project findByProjectId(int projectId);
}
Project-entity:
#Component
#Entity
#Table(name = "xxx.projects")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class Project implements Serializable {
private static final long serialVersionUID = -8071542032564334337L;
private int projectId;
private int projectType;
#Id
#Column(name = "project_id")
public int getProjectId() {
return projectId;
}
public void setProjectId(int projectId) {
this.projectId = projectId;
}
#Column(name = "project_type")
public int getProjectType() {
return projectType;
}
public void setProjectType(int projectType) {
this.projectType = projectType;
}
}
Storing the Project in the database works as expected. I can have a look at the database and the correct values are inserted. Now I have a different entity that I'm creating the same way as the project and that I want to save in the database via a CrudRepository.
Here the problem begins. Hibernate is not inserting the values that I have set. Hibernate always only inserts null into the database. Reading the values in my Spring application is working as expected. I think that Hibernate is not using the proxy of the entity but the underlying blueprint of the object. How can I force Hibernate to use the proxy with the correct values?
Repository:
#Repository("DataInput001Repository")
public interface DataInputRepository extends CrudRepository<DataInput, DataInputId> {}
Entity:
#Component("DataInput001")
#Entity
#Table(name = "xx.data_input_001")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#IdClass(DatanputId.class)
public class DataInput implements Serializable {
private static final long serialVersionUID = -6941087210396795612L;
#Id
#Column(name = "project_id")
private int projectId;
#Column(name = "income")
private String income;
#Column(name = "income_increase")
private String incomeIncrease;
/* Getter + Setter */
}
Service:
#Service("DataInputService001")
public class DataInputServiceImpl implements DataInputService {
#Resource(name = "DataInputMapper001")
DataInputMapperImpl dataInputMapper;
#Resource(name = "DataInput001Repository")
DataInputRepository dataInputRepository;
#Resource(name = "DataInput001")
DataInput datanInput;
#Transactional
public void createDataInput(String json) throws Exception {
dataInputMapper.mapDataInput(json);
dataInputRepository.save(dataInput);
}
public DataInput getDataInput() {
return dataInput;
}
public void setDataInput(DataInput dataInput) {
this.dataInput = dataInput;
}
}

Java JPA Preventing Proxies from calling db

I have a spring boot (1.5.4.RELEASE) project using Java 8. I have an entity and it's related domain class like this:
#Entity
#Table(name = "Foo", schema = "dbo")
public class FooEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private int id;
#Column(name="Name")
private String name;
#Column(name="Type")
private String type;
#Column(name="Color")
private String color;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Car")
private Car car;
//getter and setter
}
public class Foo {
private int id;
private String name;
private String type;
private String color;
private Car car;
//Constructors and getters
}
I want to create a repository that fetches this Foo object from the DB but only fetching the complex fields if the user asks for them to prevent unnecessary join statements. The repo looks like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain().apply(entity);
}
}
So a barebones call for a Foo object will return the Entity with values for all the fields except for the car field. If the user wants car information then they have to explicitly call withCar.
Here is the mapper:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return entity -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), e.getCar());
};
}
}
The problem is when you do e.getCar() if the value is not there (i.e. there's a proxy present) JPA will go out and fetch it for you. I don't want this to be the case. It will just grab the values and map them to the domain equivalent if it's not there then null.
One solution that I've heard (and tried) is calling em.detach(entity); however, this doesn't work as I intended because it throws an exception when you try to access getCar and I've also heard this is not best practice.
So my question is what is the best way to create a repo using a builder pattern on a JPA entity and not have it call the DB when trying to map.
You could create a utility method that will return null if the given object is a proxy and is not initialized:
public static <T> T nullIfNotInitialized(T entity) {
return Hibernate.isInitialized(entity) ? entity : null;
}
Then you can call the method wherever you need it:
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), nullIfNotInitialized(e.getCar()));
Just map it to a new object and leave out the Car relation, this is the standard approach. You can use MapStruct and just ignore the car field during mapping: http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings
Just don't map the car... Map a field holding the ID and use another method to get the actual Car. I would use a distinctive method name, to differentiate it from the other getters.
class FooEntity {
#Column
private int carId;
public int getCarId() {
return carId;
}
public void setCarId(int id) {
this.carId = id;
}
public Car fetchCar(CarRepository repo) {
return repo.findById(carId);
}
}
You can write query on top of JPA
#Query("select u from Car c")
import org.springframework.data.repository.CrudRepository;
import com.example.model.FluentEntity;
public interface DatabaseEntityRepository extends CrudRepository<FooEntity , int > {
}
As you said
I don't want this to be the case. It will just grab the values and map them to the domain equivalent, if it's not there then null.
Then you just set it to null, because the field car will always not be there.
Otherwise, if you mean not there is that the car not exists in db, for sure a subquery(call the proxy) should be made.
If you want to grab the car when call Foo.getCar().
class Car {
}
class FooEntity {
private Car car;//when call getCar() it will call the proxy.
public Car getCar() {
return car;
}
}
class Foo {
private java.util.function.Supplier<Car> carSupplier;
public void setCar(java.util.function.Supplier<Car> carSupplier) {
this.carSupplier = carSupplier;
}
public Car getCar() {
return carSupplier.get();
}
}
class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return (FooEntity e) -> {
Foo foo = new Foo();
foo.setCar(e::getCar);
return foo;
};
}
}
Make sure you have the db session ,when you call Foo.getCar()
You could try adding state to your repository and influence the mapper. Something like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
private boolean withCar = false;
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
withCar = true;
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain(withCar).apply(entity);
}
}
In your mapper, you then include a switch to enable or disable car lookups:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain(boolean withCar) {
return e -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), withCar ? e.getCar() : null);
};
}
}
If you then use new FooRepository().getFooByName("example").fetch() without the withCar() call, e.getCar() should not be evaluated inside FooMapper
You may want to use the PersistentUnitUtil class to query if an attribute of entity object is already loaded or not. Based on that you may skip the call to corresponding getter as shown below. JpaContext you need to supply to user entity bean mapper.
public class FooMapper {
public Function<FooEntity, Foo> mapEntityToDomain(JpaContext context) {
PersistenceUnitUtil putil = obtainPersistentUtilFor(context, FooEntity.class);
return e -> {
return new Foo(
e.getId(),
e.getName(),
e.getType(),
e.getColor(),
putil.isLoaded(e, "car") ? e.getCar() : null);
};
}
private PersistenceUnitUtil obtainPersistentUtilFor(JpaContext context, Class<?> entity) {
return context.getEntityManagerByManagedType(entity)
.getEntityManagerFactory()
.getPersistenceUnitUtil();
}
}

Deleting a Node and all of its relations in Neo4j

I am trying to delete all Type's from my Neo4j database. I have a repository for the Type class, typeRepository, which I call typeRepository.deleteAll();. However, not everything is deleted. Only its node is deleted leaving the BusinessLogic node alive in the database. I am not sure what else to try at this point, since it name of the method implies that it will delete all things including itself and things related to itself. Here is how my persisted class looks, which extends a base type of object that my database contains:
#NodeEntity
public class BaseType {
#GraphId
private Long id;
#Indexed(unique=true) String uid;
private String name;
BaseType() {}
BaseType(String name) {
this.name = name;
}
}
,
public class Type extends BaseType {
#RelatedTo(type="businessLogic")
#Fetch
private BusinessLogic businessLogic;
public Type() {super();}
public Type(String name, BusinessLogic businessLogic) {
super(name);
this.businessLogic = businessLogic;
}
}
,
#NodeEntity
#XmlAccessorType(XmlAccessType.NONE)
public class BusinessLogic implements Serializable {
#GraphId
private Long id;
private static final long serialVersionUID = -634875134095817304L;
#XmlElement
private String create;
public void setCreate(String create) {
this.create = create;
}
public String getCreate() {
return create;
}
}
I only store the Type instances, and I do that by calling
typeRepository.save(new Type(name, businessLogic));.
I don't think SDN is doing cascading delete on its own. So, why don't you first delete the BusinessLogic objects via their respective repository, and then the Type objects?

Struts2 + Json Serialization of items

I have the following classes:
public class Student {
private Long id ;
private String firstName;
private String lastName;
private Set<Enrollment> enroll = new HashSet<Enrollment>();
//Setters and getters
}
public class Enrollment {
private Student student;
private Course course;
Long enrollId;
//Setters and Getters
}
I have Struts2 controller and I would like to to return Serialized instance of Class Student only.
#ParentPackage("json-default")
public class JsonAction extends ActionSupport{
private Student student;
#Autowired
DbService dbService;
public String populate(){
return "populate";
}
#Action(value="/getJson", results = {
#Result(name="success", type="json")})
public String test(){
student = dbService.getSudent(new Long(1));
return "success";
}
#JSON(name="student")
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
}
It returns me the serializable student object with all sub classes, but I would like to have only student object without the hashset returned .
How can I tell Struts to serialize only the object?
I do have Lazy loading enabled and hashset is returned as proxy class.
See the answer here which shows the use of include and exclude properties. I don't think the example clearly shows excluding nested objects however I have used it for this purpose. If you still have issues I'll post a regex which will demonstrate this.
Problem with Json plugin in Struts 2
Edit:
Here is an example of using exclude properties in an annotation which blocks the serialization of a nested member:
#ParentPackage("json-default")
#Result(type = "json", params = {
"excludeProperties",
"^inventoryHistory\\[\\d+\\]\\.intrnmst, selectedTransactionNames, transactionNames"
})
public class InventoryHistoryAction extends ActionSupport {
...
inventoryHistory is of type InventoryHistory a JPA entity object, intrnmst references another table but because of lazy loading if it were serialized it would cause an Exception when the action is JSON serialized for this reason the exclude parameter has been added to prevent this.
Note that
\\
is required for each \ character, so a single \ would only be used in the xml where two are required because of escaping for the string to be parsed right.
#Controller
#Results({
#Result(name="json",type="json"
, params={"root","outDataMap","excludeNullProperties","true"
,"excludeProperties","^ret\\[\\d+\\]\\.city\\.province,^ret\\[\\d+\\]\\.enterprise\\.userinfos","enableGZIP","true"
})
})
public class UserinfoAction extends BaseAction {
#Action(value="login")
public String login(){
if(jsonQueryParam!=null && jsonQueryParam.length()>0)
{
user = JsonMapper.fromJson(jsonQueryParam, TUserinfo.class);
}
Assert.notNull(user);
//RESULT="ret" addOutJsonData: put List<TUserinfo> into outDataMap with key RESULT for struts2 JSONResult
addOutJsonData(RESULT, service.login(user));
return JSON;
}
public class TUserinfo implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private String userid;
private String username;
private String userpwd;
private TEnterpriseinfo enterprise;
private String telphone;
private TCity city;
......
}
public class TEnterpriseinfo implements java.io.Serializable {
private String enterpriseid;
private String enterprisename;
private Set<TUserinfo> userinfos = new HashSet<TUserinfo>(0);
.......}
before set the excludeProperties property,the result is below:
{"ret":[
{
"city":{"cityename":"tianjin","cityid":"12","cityname":"天津"
,"province": {"provinceename":"tianjing","provinceid":"02","provincename":"天津"}
}
,"createddate":"2014-01-07T11:13:58"
,"enterprise":{"createddate":"2014-01-07T08:38:00","enterpriseid":"402880a5436a227501436a2277140000","enterprisename":"测试企业2","enterprisestate":0
,"userinfos":[null,{"city":{"cityename":"beijing","cityid":"11","cityname":"北京","province":{"provinceename":"beijing","provinceid":"01","provincename":"北京市"}
},"comments":"ceshi","createddate":"2004-05-07T21:23:44","enterprise":null,"lastlogindate":"2014-01-08T08:50:34","logincount":11,"telphone":"2","userid":"402880a5436a215101436a2156e10000","username":"0.5833032879881197","userpwd":"12","userstate":1,"usertype":0}]
}
,"lastlogindate":"2014-01-08T10:32:43","logincount":0,"telphone":"2","userid":"402880a5436ab13701436ab1b74a0000","username":"testUser","userpwd":"333","userstate":1,"usertype":0}]
}
after set the excludeProperties property,there are not exist province and userinfos nodes, the result is below:
{"ret":
[{
"city":{"cityename":"tianjin","cityid":"12","cityname":"天津"}
,"createddate":"2014-01-07T11:13:58"
,"enterprise":{"createddate":"2014-01-07T08:38:00","enterpriseid":"402880a5436a227501436a2277140000","enterprisename":"测试企业2","enterprisestate":0}
,"lastlogindate":"2014-01-08T11:05:32","logincount":0,"telphone":"2","userid":"402880a5436ab13701436ab1b74a0000","username":"testUser","userpwd":"333","userstate":1,"usertype":0
}]
}

Categories

Resources