My application has entities with nameEn and nameDe for english and german. But only english being used now. Since there are so many entities available, I wanted to have a generic class which can return the selected language entries,but for multiple entries my approach didn't work.
#Entity
#Table(name="employee")
public class Employee implements java.io.Serializable {
// Fields
private Integer id;
private String nameEn;
private String nameDe;
//Getter, Setter Methods
}
#Entity
#Table(name="address")
public class Address implements
java.io.Serializable {
private String descriptionEn;
private String descriptionDe;
}
public interface ILabelText {
String getNameEn();
String getNameDe();
String getDescriptionEn();
String getDescriptionDe();
}
public abstract class LabelText implements ILabelText, Serializable {
private static final long serialVersionUID = 1L;
protected String descriptionEn;
protected String descriptionDe;
private Logger log = Logger.getLogger(LabelText.class);
String language = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
public String getDescription() {
log.info("Language Selected is " + language);
if (language.equals("De")) {
return getDescriptionDe();
} else {
return getDescriptionEn();
}
}
public String getName() {
log.info("Language Selected is " + language);
if (language.equals("De")) {
return getNameDe();
} else {
return getNameEn();
}
}
}
//In Xhtml, based on selected locale, display value accordingly
<h:outputText value="#{emp.getName()}" />
<h:outputText value="#{add.getDescription()}" />
You can create an entity Lang like this
#Entity
public class Lang implements Serializable
{
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
#NotNull
private String key;
#NotNull
private String translation;
}
and use it in your Address like this
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#MapKey(name = "key")
protected Map<String, Lang> name;
Then you are able to access the correct language in JSF:
<h:outputText value="#{emp.name[userLocale].translation}" />
The expression userLocale should be resolved to your language key (en, de, ...) or can be hardcoded e.g. #{emp.name['en'].translation}.
Is more easy you create a table with translations:
e.g:
People -> All of your persons
PersonTranslations
People | id
PersonTranslations | locale; person_id;
then on your Person class you set the language for all attributes on predicate
Person.description (this will search on PersonTranslation using a person_id key, and a locale)
some like that PersonTranslation.find(1, 'en');
Related
I have a little problem in java and I didn't find any solution.
In class CustomerOrder I have a list of Enum type. I want to store in database key and description of enum. How can I do to save key and description?
Enum:
#Entity
public enum MenuList{
SOUP(15.00),
PIZZA_CHEESE_S(20.00),
PIZZA_CHEESE_M(30.00),
KEBAB(15.00),
PASTA(18.00),
FRENCH_FRIES(10.00),
STEAK(25.00),
SAUCE(3.00);
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
......}
Class customer order
#Entity
public class CustomerOrder {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private Integer noOfTable;
#OneToMany(mappedBy="customerOrder")
#Enumerated(EnumType.STRING)
private List<MenuList> productsOrdered;
private Double totalOrder;
#ManyToOne
....}
Take a look at AttributeConverter!
https://docs.oracle.com/javaee/7/api/javax/persistence/AttributeConverter.html
class MenuListConverter implements AttributeConverter<MenuList, String> {
private final static String DELIMITER = ":";
#Override
public String convertToDatabaseColumn(final MenuList menu) {
return String.format("%s%c%s", menu.name(), DELIMITER, "put your id here");
}
#Override
public String convertToEntityAttribute(final String value) {
if( value == null || value.isEmpty()) {
return null;
}
final String name = value.split(DELIMITER)[0];
return MenuList.valueOf(name);
}
}
Then, in your entity,
#Column(name = "MENU")
#Convert(converter = MenuListConverter.class)
private MenuList menu;
You could probably use a HashMap. Something like
HashMap<String, String> map = new HashMap<String, String>() {{
put("SOUP", "Classic noodle Soup");
put("FRENCH_FRIES", "Regular french fries");
}};
then you can get the descriptions with map.get(KEY);
I am trying to have a class that has a certain list of objects (specified by another class) persisted in the database as a string (use JPA Converter - all good).
And then I want to use Specification to search inside that string.
What is the best way to create the predicates? I don't seem to understand the connection bettween the AttributeConverter and the Expression in the Specification.
The parent class:
#Entity #Table
public class A {
#Column #Id #GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column
private String name;
#Enumerated(EnumType.STRING)
private SomeType type;
#Column(length=1000) #Convert(converter = BConverter.class)
private List<B> bList;
private Integer no;
}
The listed object class:
public class B{
private String type;
private Integer quantity;
}
The converter:
#Converter
public class BConverter implements AttributeConverter<List<B>, String> {
private static final String SEPARATOR = ":";
private static final String LIST_SEPARATOR = ";";
#Override public String convertToDatabaseColumn(List<B> bList) {
return bList.stream().map(e->convertToString(e)).collect(Collectors.joining(LIST_SEPARATOR));
}
#Override public List<B> convertToEntityAttribute(String str) {
if(str==null || str.isEmpty() ) return null;
return Arrays.stream(str.split(LIST_SEPARATOR)).map(e->convertFromString(e)).collect(Collectors.toList());
}
private String convertToString(B b){
if(entity==null) return null;
return b.getType().toString() +SEPARATOR+ b.getQuantity().toString();
}
private B convertFromString(String subStr){
if(subStr==null || subStr.isEmpty() ) return null;
String[] pair = subStr.split(SEPARATOR);
return new B(pair[0],Integer.valueOf(pair[1]));
}
}
In the database should look something like:
Table A:
id: 1;
name: "Some Name";
type: "THISTYPE";
blist: "TYPE1:11;TYPE2:22";
no: 0;
id: 2;
name: "Other Name";
type: "THISTYPE";
blist: "TYPE1:45;TYPE2:56";
no: 12;
I would then like to create Specifications to search over this table for the attributes inside the bList.
For example, search by an entity that contains a B object where type=TYPE1 and a quantity>=30.
public static Specification<A> customSpecification(String type, Integer value) {
return (root, query, builder) -> ///?????????
}
Is there a way to use such specifications where the DB attribute is a String but JAVA only sees the objects?
Have to beans:
#Entity
#Table(name="book")
public class Book {
#Id
#Column(name="id_book")
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy="increment")
private int id;
#Column
#Size(min=1,max=100)
private String title;
#Column
#Size(min=1,max=400)
private String description;
#Column
private Integer year=0;
#ManyToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER)
#Fetch (FetchMode.SELECT)
#JoinTable(name="book_author",
joinColumns={#JoinColumn(name="book_id_book")},
inverseJoinColumns= {#JoinColumn(name="author_id_author")})
private List<Author> author=new ArrayList<Author>();
//getters/setters
}
and:
#Entity
#Table(name="author")
public class Author {
#Id
#Column(name="id_author")
#GeneratedValue
private Integer id;
#Column
private String name;
#Column
private String surname;
#ManyToMany(mappedBy="author")
private Set<Book> book=new HashSet<Book>();
//getters/setters
}
In my jsp I'm have form for enter data about book, and multiple list for select author(s) from DB, problem only in select authors, therefore give only this code:
<sf:select multiple="true" path="author" items="${authors}" size="7" >
</sf:select>
Where ${authors} - List with objects Author from DB. Use POST request.
In my controller for this page have this (I know it's not correct):
#RequestMapping(value="/addbook", method=RequestMethod.POST)
public String addBook(Book book){
hibarnateService.saveBook(book);
return "redirect:/books";
}
When I'm create book without select authors, but enter another information, all fine, book save in DB. When select some authors get this - The request sent by the client was syntactically incorrect.
Problem solved by add in controller:
#InitBinder
protected void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Author.class, new Editor(hibarnateService));
}
and create class:
public class Editor extends PropertyEditorSupport {
private final Dao hibernateService;
public Editor(Dao hibernateService){
this.hibernateService=hibernateService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException{
Author author=hibernateService.getAuthor(Integer.parseInt(text));
setValue(author);
}
}
P.S. What wrong with me? I can't find the right answer myself until I ask here)
You will need to implement initBinder in your controller, below can be tentative code (not tested)
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(List.class, "authors ", new CustomCollectionEditor(List.class)
{
#Override
protected Object convertElement(Object element)
{
Long id = null;
if(element instanceof Long) {
//From the database 'element' will be a Long
id = (Long) element;
}
return id != null ? authorService.loadAuthorById(id) : null;
}
});
}
Conserider following table structure
country
=======
id
code
name_id
label
======
id
code
label_value_id
translations
=============
id
ref_id
language
value
Now I need to find a JPA mapping to map country:name_id and label:label_value_id to translations ref_id. I've been googling after the right english term to explain this situation but am coming up empty on decent hits. So the database can hold records as in this example
country
id: 1, code: BE, name_id: 30
label
id: 1, code: LABELA, label_value_id: 31
translations
id: 1, ref_id: 30, language: EN, value: BELGIUM
id: 2, ref_id: 30, language: NL, value: BELGIE
id: 3, ref_id: 31, language: EN, value: ALPHA_A
id: 4, ref_id: 31, language: NL, value: ALFA_A
In Java I have the 3 classes
Country, Label and Translation where I will have #OneToMany relations on Country and Label to Translation that both should map on ref_id but I have no idea how to write my #OneToMany code to achieve this. Any hints in the right direction would be very appreciated, a solution or manual
=====
Update 2013-03-23
As stated by Joop, using a discriminator is the sollution BUT it didn't work out of the box. I was forced to use a hibernate annotation #DiscriminatorOptions(force=true). If you don't add it , hibernate totally ignores the Discriminator in it's SQL queries when fetching the needed collections.
#Entity
#Table(name = "testtranslations")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DTYPE", discriminatorType = DiscriminatorType.STRING)
#DiscriminatorOptions(force=true)
public class TestTranslation extends DomainObject {
/**
*
*/
private static final long serialVersionUID = -6211853644196769521L;
private long id;
private String language;
private String value;
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="testtranslations_seq_gen")
#SequenceGenerator(name="testtranslations_seq_gen", sequenceName="TESTTRANSLATIONS_SEQ")
#Column(name="testtranslation_id")
public long getId() {
return id;
}
#Column(name="language", length=3, nullable=false)
public String getLanguage() {
return language;
}
#Column(name="value")
public String getValue() {
return value;
}
#Override
public void setId(long id) {
this.id = id;
}
public void setLanguage(String language) {
this.language = language;
}
public void setValue(String value) {
this.value = value;
}
}
#Entity
#Table(name = "testcountries")
public class TestCountry extends DomainObject {
/**
*
*/
private static final long serialVersionUID = -9207081478447113501L;
private long id;
private String code;
private List<NameTranslation> name;
private List<DescriptionTranslation> description;
#Id
#GeneratedValue(strategy=GenerationType.AUTO, generator="testcountries_seq_gen")
#SequenceGenerator(name="testcountries_seq_gen", sequenceName="TESTCOUNTRIES_SEQ")
#Column(name="country_id")
public long getId() {
return id;
}
#Column(name="iso_code", length=3, nullable=false)
public String getCode() {
return code;
}
#OneToMany(mappedBy="refId")
public List<NameTranslation> getName() {
return name;
}
#OneToMany(mappedBy="refId")
public List<DescriptionTranslation> getDescription() {
return description;
}
#Override
public void setId(long id) {
this.id = id;
}
public void setCode(String code) {
this.code = code;
}
public void setName(List<NameTranslation> name) {
this.name = name;
}
public void setDescription(List<DescriptionTranslation> description) {
this.description = description;
}
}
#Entity
#DiscriminatorValue("NAMETRANSLATION")
public class NameTranslation extends TestTranslation {
/**
*
*/
private static final long serialVersionUID = 7197732491071768673L;
private TestCountry refId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "refId", nullable=false)
public TestCountry getRefId() {
return refId;
}
public void setRefId(TestCountry refId) {
this.refId = refId;
}
}
#Entity
#DiscriminatorValue("DESCTRANSLATION")
public class DescriptionTranslation extends TestTranslation {
/**
*
*/
private static final long serialVersionUID = -4128287237786410515L;
private TestCountry refId;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "refId", nullable=false)
public TestCountry getRefId() {
return refId;
}
public void setRefId(TestCountry refId) {
this.refId = refId;
}
}
I made the needed hibernate mappings and wrote a DbUnit test to load a TestCountry by id with following data
<TESTCOUNTRIES COUNTRY_ID="1" VERSION="0" ISO_CODE="BE" />
<TESTTRANSLATIONS TESTTRANSLATION_ID="1" VERSION="0" LANGUAGE="EN" VALUE="Belgium" REFID="1" DTYPE="NAMETRANSLATION" />
<TESTTRANSLATIONS TESTTRANSLATION_ID="2" VERSION="0" LANGUAGE="NL" VALUE="Belgie" REFID="1" DTYPE="NAMETRANSLATION" />
<TESTTRANSLATIONS TESTTRANSLATION_ID="3" VERSION="0" LANGUAGE="EN" VALUE="BelgiumDesc" REFID="1" DTYPE="DESCTRANSLATION" />
<TESTTRANSLATIONS TESTTRANSLATION_ID="4" VERSION="0" LANGUAGE="NL" VALUE="BelgieDesc" REFID="1" DTYPE="DESCTRANSLATION" />
I hope this will help other people in the future, I'm just sad there isn't a JPA sollution and I had to be forced to use a hibernate annotation.
A bit difficult as you have a foreign key for either table 1 or table 2.
There does exist a discriminator concept in JPA, for table inheritance. One then derives two tables from a common abstract table, and has different discriminator fields. An example. This works a bit differently though.
P.S. look for a better example using search keys discriminatorValue and discriminatorColumn.
This question already has answers here:
Conversion Error setting value for 'null Converter' - Why do I need a Converter in JSF?
(2 answers)
Closed 7 years ago.
I am working on a web application using PrimeFaces, JPA, Hibernate and JSF 2.0.
I have two entities Students and Course with an many to one relationship.
#Entity
#Table(name = "STUDENTS")
public class Students extends MainUsers implements Serializable {
private String firstName;
private String lastName;
private long mobile;
private String jobTitle;
private int duration;
#Temporal(TemporalType.TIMESTAMP)
private Date startDate;
#Temporal(TemporalType.TIMESTAMP)
private Date endDate;
#ManyToOne
#JoinColumn(name = "course_id", nullable = true)
private Course company;
// Getters and setters.
}
#Entity
#Table(name = "COURSE")
#NamedQueries({
#NamedQuery(name = "course.findCourseByCourseId", query = "select c from Course c where c.Id =:id"),
#NamedQuery(name = "course.findCourseByCourseName", query = "select c from Course c where c.courseName =:courseName") })
public class Course implements Serializable {
public static final String FIND_COURSE_BY_COURSE_ID = "course.findByCourseId";
public static final String FIND_COURSE_COURSE_NAME = "course.findByCourseName";
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int Id;
private String courseName;
#OneToMany(targetEntity = Students.class, mappedBy = "course", fetch = FetchType.LAZY)
private List<Students> students;
// Getters and setters.
#Override
public int hashCode() {
return getId();
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Course) {
Course course = (Course) obj;
return course.getId() == Id;
}
return false;
}
}
I have a registration page where it takes student's names, email, password and course. For selecting the course I have used this
<h:outputText value="Course :" />
<p:selectOneMenu value="#{courseMB.course}">
<f:selectItem itemLabel="Select one Course" itemValue="" />
<f:selectItems value="#{courseMB.allCourses}" var="crs"
itemLabel="#{crs.courseName}" itemValue="#{crs.id}" />
<f:converter converterId = "courseConverter"/>
</p:selectOneMenu>
To create a student I have the following method in StudentMB class:
public void createStudent() {
String test = student.getEmail();
if (getStudentFacade().isStudentExist(test)){
System.out.println(test);
} else {
try {
student.setActivation_key(hashKey());
student.setRole(Role.STUDENT);
student.setActive(0);
getStudentFacade().createStudent(student);
displayInfoMessageToUser("Created with success");
SendEmail.Send(username, password, student.getEmail(), title, linkToSend());
loadStudent();
resetStudent();
} catch (Exception e) {
displayErrorMessageToUser("Opps, we could not create the user. Try again");
e.printStackTrace();
}
}
}
I don't really know how to get the course into StudentMB and persist that into the database. The current methodology gives me an error
Conversion Error setting value 'courseID' for 'null Converter'.
I tried to use a converter but I am getting empty string into the argument.
#FacesConverter(value="courseConverter", forClass = com.model.Course.class)
public class CourseConverter implements Converter {
#Override
public Object getAsObject(FacesContext context, UIComponent component, String submittedValue) {
CourseFacade courseFacade = new CourseFacade();
int courseId;
try {
courseId = Integer.parseInt(submittedValue);
} catch (NumberFormatException exception) {
throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
"Type the name of a Course and select it (or use the dropdown)",
"Type the name of a Course and select it (or use the dropdown)"));
}
return courseFacade.findCourse(courseId);
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object modelValue) {
Course course = null;
if (modelValue instanceof Course){
course = (Course) modelValue;
StringBuilder asString = new StringBuilder();
asString.append(course.getId()+"-");
asString.append(course.getCourseName());
return asString.toString();
}
return "";
}
}
How is this caused and how can I solve it?
You need to set the select item value to the Course itself, not to its id.
Replace
itemValue="#{crs.id}"
by
itemValue="#{crs}"
Otherwise the converter would only get the #{crs.id} as modelValue in getAsString() and thus return an empty string. This empty string is in turn submitted back to the server through getAsObject() which in turn causes the exception.
You should have thrown a ConverterException in the else part of the if (modelValue instanceof Course) so that it was immediately clear that the converter isn't suitable for the given model value.
See also:
Our selectOneMenu wiki page