I have a customer's database that has a collection, in which the document fields can vary between each other. There are some constant fields I can rely on, but as for the rest - I have no way of narrowing the field list as the customer wants the solution to be dynamic.
My question is - can I somehow implement a generic mapping that would return, let's say, a map of document's fields using Spring Data?
edit:
Thanks for the tips. I've tried getting the generic Object (hoping I'd be able to convert it into a map) using the entity:
#Document(collection = "Data")
public class DataEntity {
#Id
private String id;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
but fetching the object by the ID using MongoRepository produces an object with data field set to null.
I'm using SpringBoot 1.3.1.RELEASE with spring-boot-starter-data-mongodb 1.3.1.RELEASE.
You can use a Map for dynamic properties like below. Is this what you are looking for?
#Document(collection = "computers")
public class Computer {
#Id
private String id;
#Field("name")
private String name;
//Other constant fields
#Field("properties")
private Map<String, Object> properties;
}
Related
i have problem with saving data in DB.I'm new in Spring Boot. When i run my program the result of writen data is: packagename#randomcode example:com.abc.patient.Patient#6e3e681e
This is my Entity class - Patient.java
#Entity
public class Patient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
// getter, setter, constructor, etc
}
This is my CrudRepo PatientRepository.java
public interface PatientRepository extends CrudRepository<Patient,Integer> {
}
This is my Service class PatientService.java
#Service
public class PatientService {
#Autowired
private PatientRepository patientRepository;
public void savePatient (String name) {
Patient patient = new Patient(name);
patientRepository.save(patient);
}
public Optional<Patient> showPatient(int id) {
return patientRepository.findById(id);
}
public List<Patient> showAllPatients() {
List<Patient> patients = new ArrayList<>();
patientRepository.findAll().forEach(patients::add);
return patients;
}
}
I think that problem in in the savePatient method in this line:
Patient patients = new Patient(name);
I checked the "name" parameter and it's in 100% correct String. I'm using Derby DB.
The only problem you have is how you are printing out your Patient class. Define a proper toString() or just debug yourself to see the resulting fields. There is no problem in your JPA implementation.
See this question for the details of default toString
Try:
public void savePatient(Patient patient) {
patientRepository.save(patient);
}
I didn't have this problem before, with other POJOs, I'm not sure what's different this time, but I can't get this working and I could not find an exact solution for this.
I have this POJO called Component (with some Hibernate annotations):
#Entity
#Table(name="component", uniqueConstraints={#UniqueConstraint(
columnNames = {"name", "component_type"})})
public class Component {
#Column(name="id")
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
#Column(name="name")
private String name;
#Column(name="component_type")
private String componentType;
#Column(name="serial_number")
private int serialNumber;
#Column(name="active_since")
private String activeSince;
#Embedded
private ComponentWearoutModel wearout;
public Component() {
}
public Component(String name, String componentType, int serialNumber, String activeSince,
ComponentWearoutModel wearout) {
this.name = name;
this.componentType = componentType;
this.serialNumber = serialNumber;
this.activeSince = activeSince;
this.wearout = wearout;
}
public ComponentWearoutModel getModel() {
return wearout;
}
public void setModel(ComponentWearoutModel wearout) {
this.wearout = wearout;
}
//more getters and setters
}
ComponentWearoutModel:
#Embeddable
public class ComponentWearoutModel {
private String componentType; //dont mind the stupid duplicate attribute
private Integer componentLifeExpectancy;
private Float componentWearOutLevel;
private Float actionThreshold;
public ComponentWearoutModel() {
}
public ComponentWearoutModel(String componentType, int componentLifeExpectancy, float componentWearOutLevel,
float actionThreshold) {
this.componentType = componentType;
this.componentLifeExpectancy = componentLifeExpectancy;
this.componentWearOutLevel = componentWearOutLevel;
this.actionThreshold = actionThreshold;
}
//getters and setters
}
The sample payload I use:
{
"name": "component name",
"componentType": "airfilter2",
"serialNumber": 573224,
"activeSince": "2016-04-10 17:38:41",
"wearout":
{
"componentType": "airfilter",
"componentLifeExpectancy": 1000,
"componentWearOutLevel": 0.24,
"actionThreshold": 0.2
}
}
And finally the resource class:
#Path("myresource")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
public class MyResource {
DatabaseManager dm = DatabaseManager.getInstance();
#PUT
#Path("Component")
public Response storeComponent(Component component){
System.out.println("reached");
System.out.println(component.getComponentType()); //okay
System.out.println(component.getModel().getComponentType()); //nullpointerexception
ComponentWearoutModel model = new ComponentWearoutModel("type", 1000, 1f, 0.2f);
component.setModel(model); //this way it's saved in the db just fine
dm.save(component);
return Response.status(Status.OK).entity(component).build();
}
}
Without the prints, only the fields which are not part of the ComponentWearoutModel class are stored in the database table, the other columns are null. So when I try to print one of them, I get an exception, I just dont understand why. If I create a ComponentWearoutModel in the resource method and add it to the component, everything is fine in the database.
UPDATE:
so my mistake was that I named the ComponentWearoutModel attribute as "wearout" in the Component.class, but the autogenerated getters and setter were called getModel/setModel and moxy could not parse my payload because of this. Solution: change the attribute name to "model" in Component class and in payload too.
Please ensure that the attribute names you are using in the POJO are same as what are being sent in the json string.
Since there are no jackson etc annotations being used in your POJO to tell it the corresponding json mapping, the underlying code will directly use the names given in json string. If you are using the string "model", the convertor code will look for a "setModel" method in your POJO.
In the above example, either call everything "model", or "wearable".
I have DTO structure like :
public class ADto{
private String name;
private String id;
private List<BDto> bdtos;
//Created constructor using fields
}
public class BDto{
private String id;
private String code;
private List<CDto> cdtos;
//Created constructor using fields
}
public class CDto{
private String mKey;
private String mVal;
//Created constructor using fields
}
Used Spring MVC for fetching the data.
Below query is working perfectly fine and binding the data :
#org.springframework.data.jpa.repository.Query("select new pkg.ADto(id,name) from AEntity a where a.id=?1")
public ADto getAData(Long id);
How can I fetch the data for the list which is in turn composed of further list using the above method?
If you want to return DTOs instead on enitites, you need to provide mapping between DTOs and entities. With JPQL query, the only option is to provide that mapping in constructor of the resulting object. Therefore, you need to add a constructor to ADto, which accepts BEntities, and map all nested entities to dtos in that constructor. Or in more object oriented way, the new constructor will accept AEntity as the only argument.
This is how it could look like:
getAData() method in the repository (JPQL is slightly modified by adding a.bEntities to result):
#org.springframework.data.jpa.repository.Query("select new pkg.ADto(id,name, a.bEntities) from AEntity a where a.id=?1")
public ADto getAData(Long id);
New constructor in ADto:
public class ADto{
private String name;
private String id;
private List<BDto> bdtos;
public ADto(String id, String name, List<BEntity> bEntities) {
this.id = id; this.name = name;
this.bdtos = new ArrayList<>();
for (BEntity b : bEntities) {
BDto bdto = new BDto(b.id, b.code, b.cEntities);
/* you need to pass cEntities and map them again in the BDto
* constructor, or you may do the apping in ADto constructor
* and only pass mapped values to BDto constructor */
}
}
}
You have to enable eager fetch:
#OneToMany(mappedBy = "adto", fetch = FetchType.EAGER)
private List<BDto> bdtos;
Then you can fetch it like this i.e.:
ADto findById(Long id); // literally!
I have a Problem with Spring - Data - Redis
I am using redis as cache for entities that i am fetching from database using JPA Repository.
Here is the entity structure :
#Entity(name = "issue_category")
public class IssueCategory extends AuditablePersistable {
private static final long serialVersionUID = 8659360844089652714L;
private Integer parentId;
private String name;
private String displayNameCRM;
private String displayNameSelfServe;
private List<IssueCategoryRule> issueCategoryRules = new ArrayList<>();
public static final Integer ROOT_PARENT_ID = 0;
// with all getter setters
}
Here is part where i am caching it:
#Override
#Cacheable(value = IC_CACHE, key = "#id")
public IssueCategory getIssueCategoriesById(Integer id) {
return issueCategoriesRepo.findById(id);
}
The Repo code for getting from db:
public interface IssueCategoryRepo extends JpaRepository<IssueCategory, Integer>
{
public IssueCategory findByName(#Param("name") String name);
public List<IssueCategory> findByParentId(#Param("parentId") Integer parentId);
public IssueCategory findById(#Param("id") Integer id);
}
The problem I am getting is
Class Cast exception when this entity is being extracted from cache
It says :
Can not cast from java.util.ArrayList to IssueCategory class.
Can anybody help.
I'm currently working good object oriented principles, and hibernate, I have this POJO, in which the properties would be dynamically populated. This is a design pattern I've read for good Object Oriented Design, in which it would be easy to add attributes to specificic object without breaking the application. My question is, how can you map this to a table, when your attributes is supposedly dynamic, I'm using an enum to limit the key value pairs for the map, but ideally it can still grow. I am only using in-memory database (h2) and I'm not going to be using the code for production use. This is for learning purposes only. see code below:
public class Transaction {
private static Map<Object, Object> properties;
public Transaction(){
if(null != properties)
properties = new LinkedHashMap<Object, Object>();
}
public Transaction(Map<Object, Object> properties){
if(null != properties)
setProperties(properties);
}
public void setProperties(Map<Object, Object> prop){
properties = prop;
}
public void setProperties(Properties property, String value){
properties.put(property, value);
}
public Map<Object, Object> getProperties(){
return properties;
}
public String getProperties(Properties property){
return (String) properties.get(property);
}
}
So I want to be able to create a table that would have this properties, dynamically,
My Enum:
public enum Properties {
Entry("Entry"), Id("Entry_ID"), Name("Name"), Credit("Credit");
private final String description;
private Properties(final String description){
this.description = description;
}
#Override
public String toString(){
return description;
}
}
I have this hibernate mapping, but as you can see this would be need to be updated everytime a field is updated, I need a generic mapping so that when I change/add the attributes, annotation or xml would be okay, see below:
<class name="Transaction" table="TRANSACTION">
<id name="id" column="ENTRY_ID">
<generator class="increment"/>
</id>
<property name="name"/>
<property name="credit" type="boolean" column="IS_CREDIT"/>
</class>
UserDefinedField on Marin Fowler's web site may be a perfect starting point for exploring general answers to this question.
As for Hibernate: It's really designed for statically binding tables to objects and you may have significant problems if you change the schema while running. You can implement the following solutions, though:
Serialized LOB (you serialize your Map into a binary field or - using JSON/XML - a text field). This is a half-and-half approach - half tabular/normal form/SQL and half not-SQL. So, if this approach is attractive, you might want to consider going all-in with a NoSQL database as discussed later
Attribute table, where your customized attributes are stored in a key-value pair table that joins to to the master table. This can be mapped in Hibernate using Indexed Collections (see section 7.2.2.2 Maps) and you would end up with something quite like in your question:
#Entity
public class Transaction {
#Id #GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
// ... snip ...
#OneToMany(mappedBy="transaction")
#MapKey(name="name")
public Map<String, String> getProperties(){
return properties;
}
public void setProperties(Map<String, String> prop){
properties = prop;
}
private Map<String, String> properties; // NB: Type has to be <String, String> because the column name is a String and you have defined the property value to be a String.
public void setProperty(Properties property, String value){
properties.put(property, value);
}
public String getProperty(String name){
return (String) properties.get(property);
}
}
#Entity
public class Property {
#Id #GeneratedValue public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
private Integer id;
#ManyToOne
public Transaction getTransaction() { return transaction; }
public void setTransaction(Transaction transaction) { this.transaction = transaction; }
private Transaction transaction;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
private String name;
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
private String description;
}
Pre-defined custom-fields, where you start with a really wide table with loads of unused columns. In this implementation you end up defining a mapping between your arbitrary property names and the pre-defined column names (getString1(), getString10(), etc)
However, a much better solution for you may be to use a NoSQL database - specifically a document-based one. These allow you to store and retrieve arbitrary data-structures (maps and lists). Interestingly, using such an approach makes binding to the data store significantly easier.
MongoDB or Redis (Java bindings at Jedis) are examples.