I have two classes, Person and Company, derived from another class Contact. They are represented as polymorphically in two tables (Person and Company). The simplified classes look like this:
public abstract class Contact {
Integer id;
public abstract String getDisplayName();
}
public class Person extends Contact {
String firstName;
String lastName;
public String getDisplayName() {
return firstName + " " + lastName;
}
}
public class Company extends Contact {
String name;
public String getDisplayName() {
return name;
}
}
The problem is that I need to make a query finding all contacts with displayName containing a certain string. I can't make the query using displayName because it is not part of either table. Any ideas on how to do this query?
Because you do the concatenation in the Java class, there is no way that Hibernate can really help you with this one, sorry. It can simply not see what you are doing in this method, since it is in fact not related to persistence at all.
The solution depends on how you mapped the inheritance of these classes:
If it is table-per-hierarchy you can use this approach: Write a SQL where clause for a criteria query, and then use a case statement:
s.createCriteria(Contact.class)
.add(Restrictions.sqlRestriction("? = case when type='Person' then firstName || ' '|| lastName else name end"))
.list();
If it is table per-concrete-subclass, then you are better of writing two queries (since that is what Hibernate will do anyway).
You could create a new column in the Contact table containing the respective displayName, which you could fill via a Hibernate Interceptor so it would always contain the right string automatically.
The alternative would be having two queries, one for the Person and one for the Company table, each containing the respective search logic. You may have to use native queries to achieve looking for a concatenated string via a LIKE query (I'm not a HQL expert, though, it may well be possible).
If you have large tables, you should alternatively think about full-text indexing, as LIKE '%...%' queries require a full table scan unless your database supports full text indexes.
If you change displayName to be a mapped property (set to the name column in Company and to a formula like first||' '||last in Person), then can query for Contract and Hibernate will run two queries both of which now have a displayName. You will get back a List of two Lists, one containing Companies and one containing Persons so you'll have to merge them back together. I think you need to query by the full package name of Contract or set up a typedef to tell Hibernate about it.
Related
I have a JPA select where you receibe a parameter then we can search using some attributes (username, email, identifier) The user only have a text field to write the criteria text.
The problem is perfomance, in the database We have about 9Millions of users registereds, and the search is too slow, using JPA,
Form:
- Value (Input text)
User sends the value in the form (He doesn't say if he is using username, email or identifier)
User (Table) Fields:
- Identifier
- Name
- Email
JPA Query:
select u from UserEntity u where u.alias LIKE lower(:query) OR u.email LIKE lower(:query) OR lower(u.identifier) LIKE lower(:query) ORDER BY u.alias
I don't know what the best method to improve the speed of the search, (We have some indexes in the table in these fields), if we remove the lower in the u.identifier field the speed improves a lot (almost instant). But we can have the identifier in a lot of ways (migrations, registers, manual client inserts..)
We have about 9 Millions of users registered, and the search is too slow, using JPA
Please note the search will be too slow regardless the technology involved in executing the query just because the SQL query itself is too slow. That being said the problem is not JPA but how to improve the SQL query.
Combining LOWER() function with LIKE operator adds a lot of overhead because the RDBMS must apply a text function to 3 fields before analyse the LIKE match.
IMHO a good approach is using a VIEW to leverage the LOWER() part on the 3 fields and then execute the query over this view (BTW mapping a View with JPA is just as simple as mapping a Table):
CRAETE VIEW user_view AS SELECT id, lower(identifier) AS identifier, lower(alias) AS alias, lower(email) AS email FROM user;
Then create an entity for searching:
#Entity
#Table("user_view")
public class UserView {
#Basic private Long id;
#Basic private String identifier;
#Basic private String alias;
#Basic private String email;
// getters and setters as required
}
And finally the JPQL query:
String jqpl = "SELECT u FROM UserView u WHERE u.alias LIKE :query OR u.email LIKE :query OR u.identifier LIKE :query";
Note that you can pass query parameter directly in lower case as a Query parameter and you can order the result list after the query execution. Both will result in the RDBMS making less effort and consequently improving the overall response time.
You can solve it using different techniques.
One is using a collate that ignores case. For example in MySQL https://dev.mysql.com/doc/refman/5.7/en/case-sensitivity.html
Another strategy is to have a derived field with an index. In DB2, for example, you can create a field 'lower_identifier generated always as lower(identifier)' and an index in the field and it will use it automatically when doing a lower search. You don't have to map the field in JPA.
I'm looking for something which would allow me to replicate something like Jackson's #JsonView functionality but on a database level. Often I feel like I've hit the wall trying to balance between query performance and having all the necessary data returned from the API.
For example I have a class structure like:
class Employee {
String name;
#ManyToOne(fetchType = EAGER)
Company company;
// A lot of other data
}
class Company {
String name;
// A lot of other data
}
Now I would like to display a list of Employees and additionally show the Company name of each employee in the table. Ideally in raw SQL world I would do something like
select e.name, c.name from Employees as e left join Companies as c on e.companyId = c.id
But what instead happens is that hibernate queries the whole company data as well including other relations and their relations which often makes it really slow. Lazy loading is also slower and doesn't work when you need to return the queried data from a controller.
Maybe there is something which would allow me to declare a data view in a manner like:
class EmployeeListView {
String name = Path.to("Employee.name");
String companyName = Path.to("Employee.company.name")
}
?
#NamedEntityGraph is the answer, for anyone concerned
Let's say I have a Person JPA entity:
#Entity
public class Person {
#Id
private Long id;
#Column(name="name")
private String name;
#Column(name="age")
private Integer age;
#Column(name="hobbies")
private List<String> hobbies;
public Person() {};
}
Using a Criteria Query, is it possible to retrieve a List<Person>, but only include each Person's name?
Looking at Java Persistence with Hibernate, I see that there's a Criteria#setResultTransformer method.
I think that I could transform my results to a ReducedPerson class that only contained a name. However, I'd like to select only the Person's name, but still get Person objects back.
Is this possible?
You're correct, you'll need to set a ResultTransformer. For what I know in this area of hibernate, which is not much, the transformers provided by Hibernate are strict and will fail if a value is missing, so I think you'll need to create your own instance of ResultTransformer. I would suggest to look at the code from AliasToBeanResultTransformer and make a lenient version of it.
You can do this using Constructor Expressions:
http://en.wikibooks.org/wiki/Java_Persistence/JPQL#Constructors
You can use your existing Person class (instances returned from your query being, of course, unmanaged) but would however need to a constructor taking the name.
SELECT NEW sample.Person(p.name) FROM Person p
Single Projection
You use an existing ResultTransformer like this
Criteria query = session.createCriteria(Person.class)
.setProjection(Projections.property("name").as("name"))
.setResultTransformer(Transformers.aliasToBean(Person.class));
List personNames = query.list();
Multiple Projections
The example List above now contains only Person objects with their name. Regullary, you want also to retrieve the id for the persons at least. You can setup multiple properties with this
ProjectionList colProjection = Projections.projectionList();
colProjection.add(Projections.property("id"), "id");
colProjection.add(Projections.property("name"), "name");
Criteria query = session.CreateCriteria(Person.class)
.setProjection(colProjection)
.setResultTransformer(Transformers.aliasToBean(Person.class));
List persons = query.list();
Key in this approach is to set the same aliases as their original property names, so we dont need to build our own ResultTransformer.
I have a model class "Journey" in my project which has several methods to delete, create and list all of the journeys. I am using heroku and a postgresql database. I need to write a method that will return all journeys that have a similar address to one specified. I know the query structure would typically be something like SELECT address FROM Journey WHERE address ~~ arguement but I don't know what functions exist to do this in the play framework.
*public static void search(String address){
//query
//return matching journey results
}*
You need to use Model's Finder for an example:
package models;
import play.db.ebean.Model;
import javax.persistence.*;
#Entity
public class Journey extends Model {
#Id
public Integer id;
public static Finder<Integer, Journey> find
= new Model.Finder<>(Integer.class, Journey.class);
// other fields
public String address;
public String country;
}
so you can easily select records with:
List<Journey> allJourneys = Journey.find.all();
List<Journey> searchedJourneys = Journey.find.where().like("address", "%foo%").findList();
Journey firstJourney = Journey.find.byId(123);
In your base case you can add this to your model:
public static List<Journey> searchByAddress(String address){
return find.where().like("address", "%"+address+"%").findList();
}
Etc. It returns whole objects with relations, so in big data sets it can be too heavy, you can or even should also use more optimized queries with Finder's chained methods like select(), fetch() etc to point which data you need at the moment.
There are also other possibilities in Ebean's API, anyway you need to declare which approach is most optimal for you.
BTW, it's worthy to examine existing sample applications, for an example computer's database to get familiar with this ORM.
Edit
For case insesitive searching there are additional Expressions i.e. ilike (instead of like) , istartsWith, iendsWith, ieq, icontainsand iexampleLike. They does the same what version without i at the beginning.
You can preview them in the API as well.
I think the best way to describe the problem is by using an example. I will keep it as simple as possible by removing unneeded details of the implementation.
Say there is a book store. The store keeps track of all books, customers, and orders using a backend database to store all the data, and a Java front-end to present them to the manager of the store.
The database contains the following relations:
Book ( id, title, author )
Customer ( id, name, tel, address )
Order ( id, date, custId, bookId )
On the other side, the Java interface uses JDBC driver to connect to the database and retrieve the data. The application consists of the following classes:
Book
BooksDataLoader
BooksTableModel
BooksView
Customer
CustomersDataLoader
CustomersTableModel
CustomersView
Order
OrdersDataLoader
OrdersTalbeModel
OrdersView
These classes use respective design guidelines and you can use the following source code as reference:
public class Book {
private String id;
private String title;
private String author;
/*
* Builder pattern is used so constructor should be hidden. Book objects
* are built in the BooksDataLoader SwingWorker thread.
*/
private Book() {}
}
public class BooksDataLoader extends SwingWorker<List<Book>, Book> {
private final BooksTableModel booksModel;
private final List<Book> books = new ArrayList<Book>();
}
public class BooksTableModel extend AbstractTableModel {
private final String columnNames = { "Book ID", "Book Title", "Book Author" };
private final List<Book> books = new ArrayList<Book>();
}
public class BooksView extends JPanel {
private final JTable booksTable;
private final BooksTableModel booksModel;
}
I am using the Builder pattern to implement the classes Book, Customer, and Order. The instances of these classes are built using data retrieved by the database inside a SwingWorker thread and are published to the view using an AbstractTableModel. So, actually the application consists of the following views (JPanels): BooksView, CustomersView, and OrdersView, each of which contains a single JTable with columns as shown below:
BooksView.booksTable: Book ID | Book Title | Book Author
CustomersView.customersTable: Customer ID | Customer Name
OrdersView.ordersTable: Order ID | Date | Customer Name | Book Title | Book Author
The problem appears when we try to resolve an instance variable which represents a foreign key in the database, to the data it links. For example, the OrdersTableModel has a List structure of all Order objects found in the database, however the columns 3, 4, and 5 of the of the OrdersView table cannot be directly accessed from an Order object since it only contains ids for the book and the customer, and not the actual data. One solution I tried was to create a static HashMap inside each of the Book, Customer, and Order classes in order to keep track of all retrieved objects but it leads to data duplication since we already have a List structure of the retrieved objects in the table model of each view.
I am looking for an efficient and extensible (object-oriented) design solution/recommendation.
Thank you in advance.
You should definitely use ORM like Hibernate or EclipseLink or whatever technology fits you. Currently JPA2 is the common standard implemented by every such tool. You define the mapping between your object and db model by using annotations or xml files.
These tools also offer ways to generate your database schema according to your object model (even the other way is possible if you have legacy schemes).
I recommend you not to make use of jpa criteria api since its design is quite flawed. There are a number of frameworks out there that help you build your queries. QueryDSL is one that seems really nice to me. I used the specification pattern (which I actually implemented using criteria api under the hood) for abstracting query construction. See http://martinfowler.com/apsupp/spec.pdf and http://adrianhummel.wordpress.com/2010/07/02/composed-specifications-using-jpa-2-0/ for first references.
And do some search on DAO pattern and repositories (a term coming from domain driven design).
Well this is a typical issue when mapping OO design on relational database tables.
Let's take as example your OrdersTableModel:
Order ID | Date | Customer Name | Book Title | Book Author
the last three columns are database foreign key ids instead of the values you want to show.
To manage this correctly you have 2 possible solution:
FIRST:
desgin the Order class like this
public class Order{
private String id;
private Date date;
private Customer customer;
private Book book;
private Author author;
get and set methods
}
Note that customer is of type CUSTOMER, book of type BOOK...
Now suppose you query the db to retreive a list of orders.
From the rows returned you have to build a list of Objects Order. Of course the db return foreign key ids for customer, book and author so:
Query table orders of db
For each row build and Orders object, fill id and date with the values of the rows
Take the foreign key id for customer. Build a new query on customer db table and take the right customer based on the id. Build a new Customer id filling its values whit the results of this second query. Assign the Customer object to the field customer of the object Orders
Same for Book and author
Add the object Order to a list
Now you have a list of suppose 10 orders
Iterate on it and fill the order table you display.
To display for example Customer field you will have
listOrders[i].getCustomer().getName(). // listOrders[i] is an Order object. getCusotmer returns a customer object. getName is a Customer's method that return the String name.
Same for book and Author
SECOND APPROACH
design Order Class like this:
public class Order{
private String id;
private Date date;
private int customer;
private int book;
private int author;
get and set methods
}
Note now the customer etc are INT fields. The hold the int reference key retreived from db
Again you query table orders.
For each row build an Order object. Fill the customer etc. values simply with the id of the db.
Now you want to display a list of orders.
Iterate on the list.
when displayng customer use
listOrders[i].getCustomer().getName().
NOTE as customer field is an int reference key the geCustomer should
Execute e query on db customer table to retreive the correct customer based on the id
Build a Customer object filling its fields
REturn the object
So the differences beetwen the two appracches:
The first build a complete Order object that contains also Customer object etc. When need to display something, you have all what you need.
Second approach build a light Order object. When need to display for example the data of the customer whe need to query the db (this is called lazy loading)
I suggest you to consider using an ORM wich really helps you mapping OO design on DB and helps you build queries that return directly objects instead of "ids"