Supposing that I'm working on MyEntity table within the name field have to be unique (but not primary key).
I'd like to define a findByName method by using QueryDSL.
My current implementation is as follows:
public MyEntity findByName(final String name) {
JPAQuery query = new JPAQuery(this.entityManager);
QMyEntity myEntity;
List<MyEntity> result = jpaQuery.from(myEntity).where(myEntity.name.eq(name)).list(myEntity);
if (result.isEmpty())
throw new EntityNotFoundException();
else if (result.size() == 1)
return result.get(0);
else
throw new PersistenceException();
}
Is mine a proper solution in order to achieve this task or is there a better way?
Use uniqueResult instead list:
public MyEntity findByName(final String name) {
JPAQuery query = new JPAQuery(this.entityManager);
QMyEntity myEntity;
MyEntity result = jpaQuery.from(myEntity).where(myEntity.name.eq(name)).uniqueResult(myEntity);
if (result == null)
throw new EntityNotFoundException();
else
return result;
}
Related
My system required to add filters,and I'm wonder if there any query that like this
SELECT *
FROM posts p
when byDate is not null then (where p.createAt BETWEEN :startDate AND :endDate)
when byType is not null then (where p.type = :type)
I knew that the query is not valid, but I want at one query to get the data wherever the request has (no filter or all filters or some of filters).
My goal is to create one query to achieve all cases.
It's usually not a good idea to write a big SQL query when you can tell in advance the actual query you want to run.
If you want to run a different query based on conditions you know before running the query, there are different approaches in JPA or Spring that you can use
Spring
You can define the different queries using Spring Data query methods?
public class PostRepository implements JpaRepository<Post, Long> {
List<Post> findByCreatedAtBetween(Date startDate, Date endDate);
List<Post> findByTypeIs(String type);
}
And then somewhere in the code, you can:
List<Post> results = null;
if (byDate != null) {
results = repository.findByCreatedAtBetween(startDate, endDate);
} else if (byType != null) {
results = repository.findByTypeIs(type);
} else {
results = repository.findAll();
}
Criteria
With criteria you can create a dynamic query at runtime and execute it:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(Post.class);
Root<User> user = query.from(Post.class);
if ((filter.getByDate() != null)) {
// byDate is not null
ParameterExpression<Date> startDate = builder.parameter( Date.class );
ParameterExpression<Date> endDate = builder.parameter( Date.class );
query.where(builder.between( b.get( "createdAt" ), startDate, endDate));
return em.createQuery(query)
.setParameter(startDate, ...)
.setParameter(endDate, ...)
.getResultList();
}
if (filter.getByType() != null) {
ParameterExpression<Date> typeParam = builder.parameter( Date.class );
query.where(builder.and(root.get("type"), typeParam));
return em.createQuery(query)
.setParameter(typeParam, ...)
.getResultList();
}
return entityManager.createQuery(query)
.getResultList();
}
}
Assuming that your entity has the fields type and createdAt.
This approach works well if you don't know in advance what's your query looks like. For example, when you don't know how many conditions you will have to add to it.
But, if I know already which query I want to run, then I prefer to use HQL/JPQL.
HQL
If your queries don't change and you already know what they look like,
I find it easier to define them with HQL:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
if (filter.getByDate() != null) {
return em.createQuery("from Post p where p.createdAt between :startDate and :endDate", Post.class)
.setParameter("startDate", ...)
.setParameter("endDate", ...)
.getResultList();
}
if (filter.getByType() != null) {
return em.createQuery("from Post p where p.type =:type", Post.class)
.setParameter("type", ...)
.getResultList();
}
return em.createQuery("from Post", Post.class)
.getResultList();
}
}
You can refactor the code to make it more elegant, but it should give you an idea. Note that if you need to reuse the same queries in different services, it might be helpful to define them using the annotation #NamedQuery.
Filters
In Hibernate (not JPA) you can also define filters. They are SQL filter conditions that one can apply at runtime:
#Entity
#FilterDef(name = Post.BY_DATE, defaultCondition = "createdAt between :startDate and :endDate", parameters = {#ParamDef(name = "startDate", type = "date"), #ParamDef(name = "startDate", type = "date") })
#FilterDef(name = Post.BY_TYPE, defaultCondition = "type = :type", parameters = #ParamDef(name = "startDate", type = "date"))
#Filter(name = Post.BY_DATE)
#Filter(name = Post.BY_TYPE)
class Post {
static final String BY_DATE = "Post.byDateFilter";
static final String BY_TYPE = "Post.byFilter"
private String type;
private Date createdAt;
...
}
Then:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
enableFilters(em);
return em.createQuery("from Post", Post.class).getResultList();
}
private void enableFilters(Filter filter, EntityManager em) {
if (filter.getByDate() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_DATE )
.setParameter("startDate", ...)
.setParameter("endDate", ...);
} else if (filter.getByType() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_TYPE )
.setParameter("type", ...);
}
}
}
I'm using EclipseLink to run some Native SQL. I need to return the data into a POJO. I followed the instructions at EclipseLink Docs, but I receive the error Missing descriptor for [Class]
The query columns have been named to match the member variables of the POJO. Do I need to do some additional mapping?
POJO:
public class AnnouncementRecipientsFlattenedDTO {
private BigDecimal announcementId;
private String recipientAddress;
private String type;
public AnnouncementRecipientsFlattenedDTO() {
super();
}
public AnnouncementRecipientsFlattenedDTO(BigDecimal announcementId, String recipientAddress, String type) {
super();
this.announcementId = announcementId;
this.recipientAddress = recipientAddress;
this.type = type;
}
... Getters/Setters
Entity Manager call:
public List<AnnouncementRecipientsFlattenedDTO> getNormalizedRecipientsForAnnouncement(int announcementId) {
Query query = em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT, AnnouncementRecipientsFlattenedDTO.class);
query.setParameter(1, announcementId);
return query.getResultList();
}
I found out you can put the results of a Native Query execution into a List of Arrays that hold Objects. Then one can iterate over the list and Array elements and build the desired Entity objects.
List<Object[]> rawResultList;
Query query =
em.createNamedQuery(AnnouncementDeliveryLog.FIND_NORMALIZED_RECIPIENTS_FOR_ANNOUNCEMENT);
rawResultList = query.getResultList();
for (Object[] resultElement : rawResultList) {
AnnouncementDeliveryLog adl = new AnnouncementDeliveryLog(getAnnouncementById(announcementId), (String)resultElement[1], (String)resultElement[2], "TO_SEND");
persistAnnouncementDeliveryLog(adl);
}
You can only use native SQL queries with a class if the class is mapped. You need to define the AnnouncementRecipientsFlattenedDTO class as an #Entity.
Otherwise just create the native query with only the SQL and get an array of the data back and construct your DTO yourself using the data.
Old question but may be following solution will help someone else.
Suppose you want to return a list of columns, data type and data length for a given table in Oracle. I have written below a native sample query for this:
private static final String TABLE_COLUMNS = "select utc.COLUMN_NAME, utc.DATA_TYPE, utc.DATA_LENGTH "
+ "from user_tab_columns utc "
+ "where utc.table_name = ? "
+ "order by utc.column_name asc";
Now the requirement is to construct a list of POJO from the result of above query.
Define TableColumn entity class as below:
#Entity
public class TableColumn implements Serializable {
#Id
#Column(name = "COLUMN_NAME")
private String columnName;
#Column(name = "DATA_TYPE")
private String dataType;
#Column(name = "DATA_LENGTH")
private int dataLength;
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
public int getDataLength() {
return dataLength;
}
public void setDataLength(int dataLength) {
this.dataLength = dataLength;
}
public TableColumn(String columnName, String dataType, int dataLength) {
this.columnName = columnName;
this.dataType = dataType;
this.dataLength = dataLength;
}
public TableColumn(String columnName) {
this.columnName = columnName;
}
public TableColumn() {
}
#Override
public int hashCode() {
int hash = 0;
hash += (columnName != null ? columnName.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof TableColumn)) {
return false;
}
TableColumn other = (TableColumn) object;
if ((this.columnName == null && other.columnName != null) || (this.columnName != null && !this.columnName.equals(other.columnName))) {
return false;
}
return true;
}
#Override
public String toString() {
return getColumnName();
}
}
Now we are ready to construct a list of POJO. Use the sample code below to construct get your result as List of POJOs.
public List<TableColumn> findTableColumns(String table) {
List<TableColumn> listTables = new ArrayList<>();
EntityManager em = emf.createEntityManager();
Query q = em.createNativeQuery(TABLE_COLUMNS, TableColumn.class).setParameter(1, table);
listTables = q.getResultList();
em.close();
return listTables;
}
Also, don't forget to add in your POJO class in persistence.xml! It can be easy to overlook if you are used to your IDE managing that file for you.
Had the same kind of problem where I wanted to return a List of POJOs, and really just POJOs (call it DTO if you want) and not #Entity annotated Objects.
class PojoExample {
String name;
#Enumerated(EnumType.STRING)
SomeEnum type;
public PojoExample(String name, SomeEnum type) {
this.name = name;
this.type = type;
}
}
With the following Query:
String query = "SELECT b.name, a.newtype as type FROM tablea a, tableb b where a.tableb_id = b_id";
Query query = getEntityManager().createNativeQuery(query, "PojoExample");
#SuppressWarnings("unchecked")
List<PojoExample> data = query.getResultList();
Creates the PojoExample from the database without the need for an Entity annotation on PojoExample. You can find the method call in the Oracle Docs here.
edit:
As it turns out you have to use #SqlResultSetMapping for this to work, otherwise your query.getResultList() returns a List of Object.
#SqlResultSetMapping(name = "PojoExample",
classes = #ConstructorResult(columns = {
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "type", type = String.class)
},
targetClass = PojoExample.class)
)
Just put this anywhere under your #Entity annotation (so in this example either in tablea or tableb because PojoExample has no #Entity annotation)
I have a set of integers that I get from db with hibernate
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "artigo_idioma", joinColumns=#JoinColumn(name="id_artigo"))
#Column(name = "id_idioma")
#Fetch(FetchMode.JOIN)
private Set<Integer> idiomas;
I can't get the whole object in the db because of a perfomance issue, and neither use lazy fetching. But I would like to map all of the Ids to the real POJO using Dozer, so that when I need the whole object I can get it by the Id I already have.
I solved it implementing a custom dozer for the Set
public class SetIdIdiomaToSetIdioma extends DozerConverter<Set, Set>{
public SetIdIdiomaToSetIdioma() {
super(Set.class, Set.class);
}
#Override
public Set<Idioma> convertFrom(Set ids, Set idiomas) {
if(ids != null && ids.size() > 0){
idiomas = new HashSet<Idioma>();
for (Object object : ids) {
if(object.getClass().equals(Integer.class)){
Integer id = (Integer) object;
if(id != null){
Idioma idioma = new Idioma();
idioma.setIdIdioma(id);
idiomas.add(idioma);
}
}
}
}
return idiomas;
}
#Override
public Set<Integer> convertTo(Set idiomas, Set ids) {
if(idiomas != null && idiomas.size() > 0){
ids = new HashSet<Integer>();
for (Object object : idiomas) {
Idioma idioma = (Idioma) object;
if(idioma != null && idioma.getIdIdioma() != null)
ids.add(idioma.getIdIdioma());
}
}
return ids;
}
}
Once it's done I have a set of the object I want and, if I need, I can search in db provinding the id for the aditional data.
I try to have such code:
Query<Card> query = ofy().load().type(Card.class);
UserData creator = ofy().load().type(UserData.class).id(creatorId).now();
if (creator != null && UserType.USER.equals(creator.getUserType())) {
query.filter("creator", creator);
}
if (orderColumnName != null) {
query.order((ascending ? "" : "-") + orderColumnName);
}
query.offset(startRow).limit(limit);
return query.list();
But it doesn't filter.
Also this filter:
UserData creator = ofy().load().type(UserData.class).id(creatorId).now();
Query<Card> query = ofy().load().type(Card.class).filter("creator", creator);
Any idea why?
EDITED
My class Card.java
#Entity
public class Card implements PersistableObject {
#Id
Long id;
#Index
Date createDate;
...
#Index
Ref<UserData> creator;
...
public UserData getCreator() {
if (creator != null) {
return creator.get();
}
return null;
}
public void setCreator(UserData creator) {
this.creator = Ref.create(creator);
}
}
My class UserData.java
#Entity
public class UserData implements PersistableObject {
#Id
Long id;
Ref<EaistoAccount> eaistoAccount;
UserType userType;
public EaistoAccount getEaistoAccount() {
if (eaistoAccount == null) {
return null;
}
return eaistoAccount.get();
}
public void setEaistoAccount(EaistoAccount aistoAccount) {
this.eaistoAccount = Ref.create(aistoAccount);
}
}
It doesn't work means that I expect to get filtered entities to corresponding UserData but it doesn't filter when I split query in a few parts also it filters when I use query in one line.
I have found a solution:
Why aren't my queries working properly? All of Objectify's
intermediate command objects are immutable. This will not work:
Query q = ofy().load().type(Foo.class); q.filter("bar", bar);
List foos = q.list(); The filter command did nothing because you
did not reassign q. You need this:
q = q.filter("bar", bar); Alternatively, chain the whole sequence in a
single statement. Read more here.
https://code.google.com/p/objectify-appengine/wiki/FrequentlyAskedQuestions
Let's say I have an object with two different one-to-many relations. Much like:
Customer 1<->M Brands and Customer 1<->M Orders
And let's say that the my object Customer has two lists related to those two objects.
I've read this example:
http://forum.springsource.org/showthread.php?50617-rowmapper-with-one-to-many-query
which explains how to do it with a single one-to-many relationship. For your convenience here's the ResultSetExtractor override:
private class MyObjectExtractor implements ResultSetExtractor{
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<Integer, MyObject> map = new HashMap<Integer, MyObject>();
MyObject myObject = null;
while (rs.next()) {
Integer id = rs.getInt("ID);
myObject = map.get(id);
if(myObject == null){
String description = rs,getString("Description");
myObject = new MyObject(id, description);
map.put(id, myObject);
}
MyFoo foo = new MyFoo(rs.getString("Foo"), rs.getString("Bar"));
myObject.add(myFoo);
}
return new ArrayList<MyObject>(map.values());;
}
}
I don't think it covers how to work with both. What would be the cleanest approach? Is there a simpler way than to iterate with conditions? Would sets be better off than lists in this case?
From your question, I assume that you have three tables; Customer, Brands, Orders. If you want to fetch the Brands and Orders properties of the Customer to your customer object, where there is no relationship between Brands and Orders, what I suggest is to use a UNION query. Something like this:
TBL_CUSTOMER
------------
CUSTOMER_ID
CUSTOMER_ACCOUNT_NO
CUSTOMER_NAME
TBL_CUSTOMER_BRANDS
-------------------
CUSTOMER_BRAND_ID - UK
BRAND_NAME
CUSTOMER_ID - FK
TBL_ORDERS
-------------------
ORDER_ID - UK
CUSTOMER_ID - FK
Query:
SELECT CUS.*, BRANDS.CUSTOMER_BRAND_ID COL_A, BRANDS.BRAND_NAME COL_B, 1 IS_BRAND FROM TBL_CUSTOMER CUS JOIN TBL_CUSTOMER_BRANDS BRANDS ON (CUS.CUSTOMER_ID = BRANDS.CUSTOMER_ID)
UNION ALL
SELECT CUS.*, ORDERS.ORDER_ID, '', 0 IS_BRAND FROM TBL_CUSTOMER CUS JOIN TBL_ORDERS ORDERS ON (CUS.CUSTOMER_ID = ORDERS.CUSTOMER_ID)
Your ResultSetExtractor will become:
private class MyObjectExtractor implements ResultSetExtractor{
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<Long, Customer> map = new HashMap<Long, Customer>();
while (rs.next()) {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer == null){
customer = new Customer();
customer.setId(id);
customer.setName(rs.getString("CUSTOMER_NAME"));
customer.setAccountNumber(rs.getLong("CUSTOMER_ACCOUNT_NO"));
map.put(id, customer);
}
int type = rs.getInt("IS_BRAND");
if(type == 1) {
List brandList = customer.getBrands();
if(brandsList == null) {
brandsList = new ArrayList<Brand>();
customer.setBrands(brandsList);
}
Brand brand = new Brand();
brand.setId(rs.getLong("COL_A"));
brand.setName(rs.getString("COL_B"));
brandsList.add(brand);
} else if(type == 0) {
List ordersList = customer.getOrders();
if(ordersList == null) {
ordersList = new ArrayList<Order>();
customer.setOrders(ordersList);
}
Order order = new Order();
order.setId(rs.getLong("COL_A"));
ordersList.add(order);
}
}
return new ArrayList<Customer>(map.values());
}
}
I think there is no better way than to iterate over all rows, extract the two different objects and add it to a List<Brand> and List<Order> within the Customer object.
So you would end up in a customer object:
public class Customer {
private List<Brand> brands;
private List<Order> orders;
....
}
There was an issue on SpringSource regarding a mutliple rowmapper: https://jira.springsource.org/browse/SPR-7698
but there's only one comment linking to a one-to-many resultset extractor:
https://github.com/SpringSource/spring-data-jdbc-ext/blob/master/spring-data-jdbc-core/src/main/java/org/springframework/data/jdbc/core/OneToManyResultSetExtractor.java
I think you're doing it right if you really need eager fetching.
If you'd need lazy fetching you could load the respective orders and brands on access during runtime. That's how Hibernate and other ORM frameworks do it. It depends on your scenario and what you do with the object.
I assume the model described by James Jithin in his answer:
TBL_CUSTOMER
------------
CUSTOMER_ID
CUSTOMER_ACCOUNT_NO
CUSTOMER_NAME
TBL_CUSTOMER_BRANDS
-------------------
CUSTOMER_BRAND_ID - UK
BRAND_NAME
CUSTOMER_ID - FK
TBL_ORDERS
-------------------
ORDER_ID - UK
CUSTOMER_ID - FK
Instead of going for one Query, I would suggest the following three:
SELECT CUS.* FROM TBL_CUSTOMER CUS
SELECT BRANDS.CUSTOMER_ID, BRANDS.CUSTOMER_BRAND_ID, BRANDS.BRAND_NAME FROM TBL_CUSTOMER_BRANDS BRANDS
SELECT ORDERS.CUSTOMER_ID, ORDERS.ORDER_ID FROM TBL_ORDERS ORDERS
Your RowCallbackHandlers would become:
private class CustomerRowCallbackHandler implements RowCallbackHandler {
private final Map<Long, Customer> customerMap;
public BrandRowCallbackHandler(Map<Long, Customer> customerMap) { this.customerMap = customerMap}
public void processRow(ResultSet rs) throws SQLException {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer == null){
customer = new Customer();
customer.setId(id);
customer.setName(rs.getString("CUSTOMER_NAME"));
customer.setAccountNumber(rs.getLong("CUSTOMER_ACCOUNT_NO"));
map.put(id, customer);
}
}
}
private class BrandRowCallbackHandler implements RowCallbackHandler {
private final Map<Long, Customer> customerMap;
public BrandRowCallbackHandler(Map<Long, Customer> customerMap) { this.customerMap = customerMap}
public void processRow(ResultSet rs) throws SQLException {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer != null){
List brandList = customer.getBrands();
if(brandsList == null) {
brandsList = new ArrayList<Brand>();
customer.setBrands(brandsList);
}
Brand brand = new Brand();
brand.setId(rs.getLong("CUSTOMER_BRAND_ID"));
brand.setName(rs.getString("CUSTOMER_BRAND_NAME"));
brandsList.add(brand);
}
}
}
private class OrderRowCallbackHandler implements RowCallbackHandler {
private final Map<Long, Customer> customerMap;
public OrderRowCallbackHandler(Map<Long, Customer> customerMap) { this.customerMap = customerMap}
public void processRow(ResultSet rs) throws SQLException {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer != null){
List ordersList = customer.getOrders();
if(ordersList == null) {
ordersList = new ArrayList<Order>();
customer.setOrders(ordersList);
}
Order order = new Order();
order.setId(rs.getLong("ORDER_ID"));
ordersList.add(order);
}
}
}
If I really had to do it, I would prefer RowCallbackHandler over ResultSetExtractor. See RowCallbackHandler api and JDBCTemplate api.
In this case you need to collect the resulting Customers collection yourself in the handler.
Sets can help to filter out duplicates.