Is it possible to perform a request that checks the string representation of a Date instance. For example
Restrictions.like("dateField", "%12%")
to retrieve dates that either have String 12 in day or 12 in month or 12 in year where "dateField" is an instance of java.util.Date
Thanks
I had the same problem and here's what I did:
First, I created my own Criterion implementation:
public class DateLikeExpression implements Criterion {
private static final long serialVersionUID = 1L;
private String propertyName;
private String value;
public DateLikeExpression(String propertyName, String value) {
this.propertyName = propertyName;
this.value = value;
}
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
String[] columns = criteriaQuery.getColumnsUsingProjection(criteria, propertyName);
if (columns.length != 1) {
throw new HibernateException("Like may only be used with single-column properties");
}
return "to_char(" + columns[0] + ", 'DD/MM/YYYY HH24:MI') like ?";
}
public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return new TypedValue[] { new TypedValue(new org.hibernate.type.StringType(),
MatchMode.START.toMatchString(value.toLowerCase()), EntityMode.POJO) };
}
}
Then, I just use it when I put the Criteria together:
criteria.add(new DateLikeExpression("dateColumnName", "26/11%"));
And that's about it. Note that this implementation is locale-dependent (pt_BR in this case) and works for postgresql, which has the to_char function. You might have to tweak it a little bit to work with your database engine.
Something like this
Restrictions.sqlRestriction("month(dateField) = 12");
Restrictions.sqlRestriction("right(year(dateField),2) = 12");
The part within the sqlRestriction depends on which database you are using.
Related
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)
In below query I wanted to group by just on date not time that's why I used TO_DATE function
select TO_DATE(e.created_dt, 'dd-mm-yy'),sum(CURRENT_BAL) from sbill.act_resource_t e group by TO_DATE(e.created_dt, 'dd-mm-yy');
so as of now its working fine with oracle but as per our business requirements application should support both oracle and mysql without write two different queries;
so do we have any solution for that which should works with both oracle and mysql ?
Note :- I am using hql
Below code :
Query query1 = entityManager.createQuery("select TO_DATE(e.createdDt, 'dd-mm-yy'),sum(CURRENT_BAL) from ActT e group by TO_DATE(e.createdDt, 'dd-mm-yy')");
List<Object> result=query1.getResultList();
Since the functions differ between the databases, we need a dynamic query. But we can do it efficiently by creating a custom conversion method, which will select the right implementation based on the current database type. And the query itself will remain common (The solution uses FluentJPA library):
public static final FormatModel DD_MM_YY = Format.dateModel(Format.DD, Format.MM, Format.YY);
public static boolean isOracle() {
return false; //should return the actual value in runtime
}
#Local
// picks the right implementation
public static Function1<String, Date> AS_DATE() {
if (isOracle())
return s -> TO_DATE(s, DD_MM_YY); //oracle
return s -> STR_TO_DATE(s, "%d-%m-%y"); // mysql
}
Now we can write a generic implementation:
#Entity
#Table(name = "act_resource_t", schema = "sbill")
#Data
public static class ActResource {
#Id
private int id;
private int currentBAL;
private String createdDT;
}
// for the result
#Tuple
#Data
public static class BalanceByDate {
private Date date;
private int balance;
}
...
public BalanceByDate balanceByDate() {
FluentQuery query = FluentJPA.SQL((ActResource e) -> {
// with lambda we inject the right implementation
Date createdDate = alias(AS_DATE().apply(e.getCreatedDT()), BalanceByDate::getDate);
Integer balance = alias(SUM(e.getCurrentBAL()), BalanceByDate::getBalance);
SELECT(createdDate, balance);
FROM(e);
GROUP(BY(createdDate));
});
return query.createQuery(em, BalanceByDate.class).getSingleResult();
}
This is the resulting SQL for MySQL:
SELECT STR_TO_DATE(t0.created_dt, '%d-%m-%y') AS date, SUM(t0.current_bal) AS balance
FROM sbill.act_resource_t t0
GROUP BY STR_TO_DATE(t0.created_dt, '%d-%m-%y')
Yesterday I posted a question regarding retrieving data from a Db and iterating over it. Someone helpfully pointed my to JDBI and away from raw data types.
Caveats: I am a tester forst and foremost and have just started to explore JDBI for some automated tests.
So, I think I've improved the previous solution but I am just struggling with implementing the best way to iterate over my dataset now it is retrieved.
Here is my method to return the dataset:
public List<FlightDataBean> lastFlightBookedResults(String supplierCode, String channel) {
String sqlQuery = getData(supplierCode, channel);
List<FlightDataBean> dataSet = jdbi.withHandle(handle ->
handle.createQuery(sqlQuery)
.mapToBean(FlightDataBean.class)
.list());
return dataSet;
}
Here is my Bean class:
public class FlightDataBean {
private String startDate;
private String origin;
private String destination;
public FlightDataBean(){
}
public FlightDataBean(String startDate, String origin, String destination) {
this.startDate = startDate;
this.origin = origin;
this.destination = destination;
}
public String getStartDate() {
return startDate;
}
public void setStartDate(String startDate) {
this.startDate = startDate;
}
public String getOrigin() {
return origin;
}
public void setOrigin(String origin) {
this.origin = origin;
}
public String getDestination() {
return destination;
}
public void setDestination(String destination) {
this.destination = destination;
}
}
Here is an example of the returned dataset, 30 rows of 3 columns:
Clearly I can retrieve individual results by doing suchlike:
List<FlightDataBean> resultSet;
resultSet = getFlightData(syndicatorName);
String startDate = (resultSet.get(0).getStartDate());
String origin = String.valueOf((resultSet.get(1)).getOrigin());
String destination = String.valueOf(resultSet.get(2).getDestination());
I just need a pointer as to the best/most efficient/safest way of iterating over all 30 because I am using the results as search test data and need to potentially use each one in turn in a further method which uses the dataset until it gets results back on a website.
I'm continuing to learn JDBI but in the meantime any help would be great
I answered your last question already with a similar answer...you can just adept the code to fit your current case:
for (FlightDataBean i : resultSet){
String startDate = i.getStartDate();
String origin = i.getOrigin();
String destination = i.getDestination();
//further code, go on from here.
}
The for-loop says nothing else, than for every Bean in your resultSet, extract the 3 values (and do the code you added afterwards).
We weren't pleased with Hibernate's usertypes for ZonedDateTime, as they don't persist the ZoneId.
Therefore we created a CompositeUserType which stores both as Timestamp and String, something along the lines of:
public class ZonedDateTimeUserType implements CompositeUserType {
public static final String PROPERTY_NAME_TIMESTAMP = "zonedDateTime";
public static final String PROPERTY_NAME_ZONEID = "zoneId";
private static final Type[] SQL_TYPES = {TimestampType.INSTANCE, StringType.INSTANCE};
private static final String[] PROPERTY_NAMES = {PROPERTY_NAME_TIMESTAMP, PROPERTY_NAME_ZONEID};
#Override
public Object nullSafeGet(final ResultSet resultSet, final String[] names, final SessionImplementor sessImpl, final Object owner)
throws SQLException {
assert names.length == 2;
Timestamp date = resultSet.getTimestamp(names[0]);
if (date == null) {
return null;
}
ZoneId zoneId = ZoneId.of(resultSet.getString(names[1]));
return getZonedDateTime4Timestamp(date, zoneId);
}
#Override
public void nullSafeSet(final PreparedStatement preparedStatement, final Object value, final int index, final SessionImplementor sessImpl)
throws SQLException {
if (null == value) {
TimestampType.INSTANCE.set(preparedStatement, null, index, sessImpl);
StringType.INSTANCE.set(preparedStatement, null, index + 1, sessImpl);
} else {
ZonedDateTime zonedDateTime = (ZonedDateTime) value;
TimestampType.INSTANCE.set(preparedStatement, getTimestamp4ZonedDateTime(zonedDateTime), index, sessImpl);
StringType.INSTANCE.set(preparedStatement, zonedDateTime.getZone().getId(), index + 1, sessImpl);
}
}
private ZonedDateTime getZonedDateTime4Timestamp(final Timestamp ts, final ZoneId zoneId) {
Instant instant = Instant.ofEpochMilli(ts.getTime());
return ZonedDateTime.ofInstant(instant, zoneId);
}
private Timestamp getTimestamp4ZonedDateTime(final ZonedDateTime zonedDateTime) {
return Timestamp.from(zonedDateTime.toInstant());
}
[...]
}
Used as
#Columns(columns = {#Column(name = "createdAt"), #Column(name = "createdAtZone")})
#Type(type = ZONED_DATE_TIME_USER_TYPE)
private ZonedDateTime createdAt;
The problem is to create a criteria query just on the Timestamp column.
It was easy with Hibernates criteria API:
Restrictions.ge("createdAt.zonedDateTime", Date.from(filter.getCreatedAt().toInstant()))
But all our attempts with JPA's criteria API failed so far. What we tried e.g. is:
cb.greaterThanOrEqualTo(root.get(Entity_.createdAt).get("zonedDateTime"), filter.getCreatedAt())
cb.greaterThanOrEqualTo(root.get("createdAt.zonedDateTime"), filter.getCreatedAt())
We found a bit hacky way, in case anyone is wondering:
public class LiteralExpression<T> extends ExpressionImpl<T> {
private final String literal;
private LiteralExpression(String literal, CriteriaBuilderImpl cb, Class<T> clazz) {
super(cb, clazz);
this.literal = literal;
}
#Override
public void registerParameters(ParameterRegistry registry) {
}
#Override
public String render(RenderingContext renderingContext) {
return literal;
}
#Override
public String renderProjection(RenderingContext renderingContext) {
return null;
}
public static <T> LiteralExpression<T> of(String literal, CriteriaBuilder cb, Class<T> clazz) {
return new LiteralExpression<>(literal, (CriteriaBuilderImpl) cb, clazz);
}
}
Usage:
LiteralExpression<Timestamp> createdAt = LiteralExpression.of(Entity_.createdAt.getName() + "." + ZonedDateTimeUserType.PROPERTY_NAME_TIMESTAMP, cb, Timestamp.class);
cb.greaterThanOrEqualTo(createdAt, filter.getCreatedAt()))
I have two classes: News and Comments with one-to-many association between them.
I am using Hibernate Criteria to fetch news from database. I would like my news to be ordered by the count of its comments.
session.createCriteria(News.class, "n");
criteria.createAlias("n.comments", "comments");
criteria.setProjection(Projections.projectionList()
.add(Projections.groupProperty("comments.id"))
.add(Projections.count("comments.id").as("numberOfComments")));
criteria.addOrder(Order.desc("numberOfComments"));
List<News> news = criteria.list();
With the following code I'm getting not the list of news but the list of objects with two Long's in each of them.
What should I do to get the list of sorted news objects?
I've found the answer to my question here:
Hibernate Criteria API - how to order by collection size?
I've added the new hibernate Order implementation:
public class SizeOrder extends Order {
protected String propertyName;
protected boolean ascending;
protected SizeOrder(String propertyName, boolean ascending) {
super(propertyName, ascending);
this.propertyName = propertyName;
this.ascending = ascending;
}
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
String role = criteriaQuery.getEntityName(criteria, propertyName) + '.' + criteriaQuery.getPropertyName(propertyName);
QueryableCollection cp = (QueryableCollection) criteriaQuery.getFactory().getCollectionPersister(role);
String[] fk = cp.getKeyColumnNames();
String[] pk = ((Loadable) cp.getOwnerEntityPersister())
.getIdentifierColumnNames();
return " (select count(*) from " + cp.getTableName() + " where "
+ new ConditionFragment()
.setTableAlias(
criteriaQuery.getSQLAlias(criteria, propertyName)
).setCondition(pk, fk)
.toFragmentString() + ") "
+ (ascending ? "asc" : "desc");
}
public static SizeOrder asc(String propertyName) {
return new SizeOrder(propertyName, true);
}
public static SizeOrder desc(String propertyName) {
return new SizeOrder(propertyName, false);
}
}
And then applied that to my criteria as
criteria.addOrder(SizeOrder.desc("n.comments"));
Now everything works fine,
thanks everyone a lot :)