In hibernate is is possible to map the same column with 2 properties of the same class, and have the possibility to reference both in queries?
Is sounds pointless but it would be worth for what I have to do.
In the underying example I could mark the second getter #Transient to keep the second getter, but I would lose the alias in queries, so I would like to keep both "myPropertyAliasOne" and "myPropertyAliasTwo".
#Entity
public MyEntity {
private String myProperty;
#Column(name="ACTUAL_VALUE")
public String getMyPropertyAliasOne(){
return myProperty;
}
#Transient
public String getMyPropertyAliasTwo(){
return myProperty;
}
}
If you want to have one column be mapped to two properties, why don't you just map one of them and set the other with the value of the first.
#Entity
public MyEntity {
#Column(name="ACTUAL_VALUE")
private String myProperty1;
private String myProperty2;
public void setMyPropert1(String str){
myProperty1 = str;
myProperty2 = str;
}
public void setMyPropert2(String str){
myProperty1 = str;
myProperty2 = str;
}
public void setMyPropert2(){
myProperty2 = myProperty1;
}
#Transient
public String getMyPropertyAliasOne(){
return myProperty1;
}
public String getMyPropertyAliasTwo(){
return myProperty2;
}
}
In this case, you manually have these properties always the same. (but I don't get it, why this redundancy is useful?)
Related
I have a very simple Entity (Person.java) that I am wanting to persist via JPA/Hibernate.
The Entity contains two fields: ID and Identification String.
The ID is a simple Integer, and is no problem. The Identification String is currently a String, but for various reasons, I want to instead use a wrapper class for String (IDString), where there are various validation methods among other things.
I am wondering how I can get JPA/Hibernate to use the wrapped string (inside the custom class IDString) when persisting the Person table in the database. I know this can probably be solved by letting the IDString be #Embeddable and then embed IDString in the Person entity with #Embedded, but I am looking for another method, mostly because IDString is in an entirely different package, and I am reluctant to have to go there and change stuff.
Googling, I found https://www.baeldung.com/hibernate-custom-types, but it seems to be mostly about more complicated cases, where you want to convert one class into another type, and I do feel that there is probably a smarter way that I am simply overlooking.
Here is the entity (in theory)
#Entity(name="Person")
#Table(name="DB_TABLE_PERSON")
public class Person implements Serializable {
#Id
Integer id;
// WHAT SHOULD I PUT HERE? I WANT TO SIMPLY USE THE STRING INSIDE IDSTRING AS THE FIELD TO PERSIST
IDString idString;
// getter and setter for ID.
public void getIdString() {
return idString.getValue();
}
public void setIdString(String in) {
idString.setValue(in);
}
}
And here is the class IDString (in theory):
public class IDString {
// I really want to be a POJO
private final String the_string;
public IdString(String input) {
if (isValid(input)) {
the_string = input;
} else {
throw new SomeCoolException("Invalid format of the ID String");
}
public boolean isValid(String input) {
// bunch of code to validate the input string
}
public String getValue() {
return the_string;
}
public void setValue(String input) {
if (isValid(input)) the_string = s;
else throw new SomeCoolException("Invalid format of the ID String");
}
I know that I could place the validation if the IDString inside the Entity, but the IDString will be used elsewhere (it's a general custom class), so I don't want to do that. Is there a simple way?
#Converter(autoApply=true) // autoApply is reasonable, if not use #Converter on field
public class IDStringConverter implements AttributeConverter<IDString,String> {
#Override
public String convertToDatabaseColumn(IDString attribute) {
return attribute != null ? attribute.getValue() : null;
}
#Override
public IDString convertToEntityAttribute(String dbData) {
return dbData != null ? new IDString(dbData) : null;
}
}
With this you should not need any other modifications in your code. One limitation of the AttributeConverter is that it maps from exactly 1 Java field to exactly 1 DB column. If you wanted to map to more columns (not the case here), you would need embeddables.
You could also put a #Column annotation on the getter:
#Entity
public class Person {
private final IdString idString = new IdString();
#Column(name = "ID_STRiNG")
public IdString getIdString() {
return idString.getValue();
}
public void setIdString(String input) {
idString.setValue(input);
}
Another solution could be to convert to/from IdString using #PostLoad and #PrePersit event handlers:
#Entity
public class Person {
#Column(name = "ID_STRiNG")
private String the_string; // no getters & setters
#Transient
private final IdString idString = new IdString();
#PostLoad
public void postLoad() {
idString.setValue(the_string);
}
#PrePersist
public void prePersist() {
the_string = idString.getValue();
}
// getters & setters for idString
public class LocationBasedRole extends AbstractEntity{
#ManyToMany(fetch=FetchType.LAZY)
private Set<Role> roles=new HashSet<Role>();
#ManyToMany(fetch=FetchType.LAZY)
private Set<Location> locations=new HashSet<Location>();
}
public class Role extends AbstractEntity{
private String name;
}
public class Location extends AbstractEntity{
private String location;
}
I have an entity named locationBasedRole which has 2 properties named roles and locations. Both roles and locations have a #ManyToMany relation with locationBasedRole.
Now I want to have one property of each in a Vaadin Table. It should be something like this,
public class UserForm extends OgsAbstractForm<User>{
MTable<LocationBasedRole> locationBasedRoleTable = new MTable<LocationBasedRole>().withHeight("100%").withWidth("100%");
#Override
protected Component createContent() {
Set<LocationBasedRole> lbRoles=new HashSet<LocationBasedRole>();
roles.addAll(locationBasedRoleFasade.findAll());
BeanItemContainer<LocationBasedRole> bean=new BeanItemContainer<LocationBasedRole>(LocationBasedRole.class);
//It returns an error on the next both lines and I know the reason, but don't know how to solve it.
// If it was no ManyToMany relation and the properties weren't a collection, it would work
bean.addNestedContainerProperty("roles.name");
bean.addNestedContainerProperty("locations.location");
bean.removeContainerProperty("persistent");
bean.removeContainerProperty("id");
bean.addAll(lbRoles);
locationBasedRoleTable.setContainerDataSource(bean);
return new VerticalLayout(locationBasedRoleTable);
}
}
When I remove the properties from the NestedContainerProperties it shows me at least something in the table.
bean.addNestedContainerProperty("roles");
bean.addNestedContainerProperty("locations");
I could use any help!
Thanks in advance!
So if I understand your question right, you want to have the Collections of your BeanItemContainer-Entity displayed in one column each?
I see two possibilities for that.
Option 1 - use a wrapper class for your Sets and use addNestedContainerBean
One possibility would be to not use Sets inside your LocationBasedRole but to use a wrapper class that extends HashSet.
Then you could use the addNestedContainerBean method.
I created a small example with the BeanItemContainer-Entity Team
public class Team {
private String teamName;
private Members teamMembers;
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public Members getTeamMembers() {
return teamMembers;
}
public void setTeamMembers(Members teamMembers) {
this.teamMembers = teamMembers;
}
}
Which consists of a name and teamMembers. The latter is of type Members:
public class Members extends HashSet<TeamMember> {
public String getMembers() {
return this.stream()
.map(member -> member.getFirstName() + " " + member.getLastName())
.collect(Collectors.joining(","));
}
}
Which is a simple wrapper for the Set that contains instances of TeamMember:
public class TeamMember {
private String firstName;
private String lastName;
private Integer age;
// getters and setters
}
As you can see in the Members class, there is a method getMembers which returns a String, containing a comma separated list of the team members names.
If we now use addNestedContainerBean("teamMembers") Vaadin tries to display all properties contained in the class Members. Vaadin will think getMembers is a getter for a String property called members and so generate a column for it.
Vaadin will also display a column "empty" because it will find the isEmpty method of Set and think empty is a property to display in a column. So we tell Vaadin to remove that column.
The final code of my example looks like:
protected Component createContent() {
Set<Team> teams=new HashSet<>();
for (int teamCounter = 0; teamCounter < 5; teamCounter++) {
Team team = createTeam();
addMembersToTeam(5, team);
teams.add(team);
}
BeanItemContainer<Team> bean=new BeanItemContainer<>(Team.class);
bean.addNestedContainerBean("teamMembers");
bean.removeContainerProperty("teamMembers.empty");
bean.addAll(teams);
teamTable.setContainerDataSource(bean);
return new VerticalLayout(teamTable);
}
The result looks like:
Option 2 - create fake getters and use addNestedContainerProperty
The only thing you have to do for this is extend your BeanItemContainer-Entity (LocationBasedRole) and create a fake getter for each Set you want to be displayed in a column. In your example those two fake getters could be public String getTheRoles() and public String getTheLocations(). Then you can use bean.addNestedContainerProperty("theRoles") and bean.addNestedContainerProperty("theLocations").
In my example my TeamMember class (the counterpart to your Role / Location classes) would still look like in the option above:
public class TeamMember {
private String firstName;
private String lastName;
private Integer age;
// getters and setters
}
And my Team class (your LocationBasedRole) would look like:
public class Team {
private String teamName;
private Set<TeamMember> teamMembers;
public String getTeamName() {
return teamName;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
public Set<TeamMember> getTeamMembers() {
return teamMembers;
}
public void setTeamMembers(Set<TeamMember> teamMembers) {
this.teamMembers = teamMembers;
}
public String getMembers() {
if (teamMembers != null) {
return teamMembers.stream()
.map(member -> member.getFirstName() + " " + member.getLastName())
.collect(Collectors.joining(","));
} else {
return "No members";
}
}
}
Now you can tell vaadin to add the (not existing) property "members" and Vaadin will find the getter getMembers and use this for generating the column. We also have to tell vaadin not to display the original "teamMembers" property. So the final code is:
protected Component createContent() {
Set<Team> teams=new HashSet<>();
for (int teamCounter = 0; teamCounter < 5; teamCounter++) {
Team team = createTeam();
addMembersToTeam(5, team);
teams.add(team);
}
BeanItemContainer<Team> bean=new BeanItemContainer<>(Team.class);
bean.addNestedContainerProperty("members");
bean.removeContainerProperty("teamMembers");
bean.addAll(teams);
teamTable.setContainerDataSource(bean);
return new VerticalLayout(teamTable);
}
and the result looks like:
I have set of objects of different types.
Ex : Employee emp, adress adr
These two classes have list of properties
public class Employee{
private Stringname;
private int age;
}
public class Adress {
private String HouseNo;
private string Street;
private string pin;
}
Each attribute is assigned with some 2 character value
Name (NA), age (AG), HouseNo(HN),Street(ST), pin(PN)
I need to construct a string with these data and delimit with a %
Output:
NA%Vidhya%AG%30%HN%80%ST%1st cross%PN%100100
Each class knows it own data best so I would let each class be responsible for generating the string. As I understand it the two char codes for each field are unique for each class and member and only used when generating the string so only the class would need them.
interface AttributeDescription {
String generateDescription();
}
public class Employee implements AttributeDescription {
//members...
public String generateDescription() {
return String.format(“NA%%%s%%AG%%%d”, name, age)
}
Then simply call this method for all objects implementing the interface.
AttributeDescription object = ...
String attr = object.generateDescription();
I don't think it can be generalized more than this given the requirements.
Update
It might be better to have a builder class for building the string to get a more unified behavior between classes. Here is an example
public class AttributeBuilder {
private builder = new StringBuilder();
public String getAttribute() {
return builder.toString();
}
public void add(String code, String value) {
if (value == null) {
return;
}
builder.append(code);
builder.append(‘%’);
builder.append(value);
builder.append(‘%’);
}
}
And then you would also have to implement add(...) methods for other data types in a similar fashion. The builder could then be used like
public String generateDescription() {
AttributeBuilder builder = new AttributeBuilder();
builder.add(“NA”, name);
builder.add(“AG”, age);
return builder.getAttribute();
}
I had the following entity mapped using JPA 2:
#Entity
public class Translation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
private String locale;
#Column(name = "business_code",insertable = true,updatable = false,length = 200,nullable = false)
private String code;
private String text;
// Gettets and setters
....
}
Then I realized than the pair (locale,code) should be unique, so I have changed the entity to have an embeddedId composed by locale, code and I removed the column id from the mapping. This way this pair would act as primary key and they could not be repeated:
As a result:
#Entity
public class Translation {
#EmbeddedId
private TranslationId translationId;
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public TranslationId getTranslationId() {
return translationId;
}
public void setTranslationId(TranslationId translationId) {
this.translationId = translationId;
}
#Override
public boolean equals(Object obj) {
return EqualsBuilder.reflectionEquals(this, obj);
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
And the embeddedId class:
#Embeddable
public class TranslationId implements Serializable{
private static final long serialVersionUID = 1L;
private String locale;
#Column(name = "business_code",insertable = true,updatable = false,length = 200,nullable = false)
private String code;
#Override
public boolean equals(Object obj){
return EqualsBuilder.reflectionEquals(this, obj);
}
#Override
public int hashCode(){
return HashCodeBuilder.reflectionHashCode(this);
}
public String getLocale() {
return locale;
}
public void setLocale(String locale) {
this.locale = locale;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
I'm using Spring data to query the data, so I have modified also my JPA repository to take in account the new composite Id:
#Repository
public interface TranslationRepository extends JpaRepository<Translation,TranslationId> {
}
So, first of all, does anyone see anything wrong here? Should I do it in another way? As my tests are not passing anymore, if I do a simple translationRepository.findAll(), I'm not getting any result (however there is data in the db), but I'm not getting any error message...
And second - if I get this to work, and then I want Spring data to query all the translations only by locale (not by code), how can I do it? As locale and code are now part of the primary key, can I query them independently?
Since your first problem was already fixed, I'll answer the second question
I want Spring data to query all the translations only by locale (not by code), how can I do it?
locale is still accessible as a single property via translationId. In JPQL you can write
SELECT t FROM Translation t WHERE t.translationId.locale = :locale
In Spring Data repository you can either use the #Query on a custom-named method
#Query("SELECT t FROM Translation t WHERE t.translationId.locale = :locale")
public List<Translation> findByLocale(#Param("locale") String locale)
or go with the slightly longer method name, but automatically handled by Spring Data
public List<Translation> findByTranslationIdLocale(String locale)
I have two classes setup like the following. I am confused as to when I need to annotate something as an foreign collection and when I do not. This may also sound silly, but nowhere in the ORMLite documentation does it say whether or not a non-foreign collection is allowed. What if I have a List of ints which get autoboxed into Integers? can I just persist this using a standard #DatabaseField above the Collection? A foreign collection, according to ORMLite, must also have back reference for it to work (a reference to the parent, given a one to many realtionship). For the example below, I am assuming you should annotate myBList as a foreign collection as well as making myA a foreign object, but how could you handle myStringList?
I Have seen sample code here but it doesn't answer my questions: http://ormlite.com/docs/examples
public class A {
private Set<B> myBList = new HashSet<B>();
private List<String> myStringList = new ArrayList<String>();
private long id;
public A(){}
public Set<B> getMyBList() {
return myBList;
}
public void setMyBList(Set<B> myBList) {
this.myBList = myBList;
}
public List<String> getMyStringList() {
return myStringList;
}
public void setMyStringList(List<String> myStringList) {
this.myStringList = myStringList;
}
public void setId(long id){
this.id = id;
}
public long getId(){
return id;
}
}
public class B {
private int myInt;
private String myString;
private A myA;
private long id;
public B(){}
public A getMyA(){
return myA;
}
public A setMyA(A a){
myA = a;
}
public int getMyInt() {
return myInt;
}
public void setMyInt(int myInt) {
this.myInt = myInt;
}
public String getMyString() {
return myString;
}
public void setMyString(String myString) {
this.myString = myString;
}
public void setId(long id){
this.id = id;
}
public long getId(){
return id;
}
}
#Robert is correct. When hibernate persists a collection (or even an array), it does so with hidden extra tables with foreign ids -- in other words hidden foreign collections. ORMLite tries to adhere to the KISS principle and so has you define the foreign collections "by hand" instead.
I've added more details about storing collections.
http://ormlite.com/docs/foreign-collection
This means that you cannot persist an Integer type because there is no foreign-id. Also, your code can define a foreign collection Collection<Order> or ForeignCollection<Order>. Either one will be set with a ForeignCollection. ORMLite does not support lists or other collection types.
If you want to save a Collection (such as an ArrayList) of objects to ORMLite the easiest way is this:
#DatabaseField(dataType = DataType.SERIALIZABLE)
private SerializedList<MyObject> myObjects;
and to get my list of objects:
public List<MyObject> getMyObjects() {
return myObjects;
}