I'm trying to create a criteria to retrieve some objects from 3 tables (Associate, Update and Detail). A Detail has reference to Associate and Update, and an Update has reference to a list of Details. My objective is to retrieve a list of Updates that has at least a Detail with null value in a specified field, given an Associate id. In JPQL was easy to do but the client said that this must be coded with criteria.
My JPQL was:
public List<Update> getUpdates(long associateId) {
TypedQuery<Update> query = em.createQuery("select distinct u from Update u, Detail dt, Associate a "
+ "where dt.update = u and dt.associate = a and a.associateId = :id and "
+ "dt.ack_date is null", Update.class);
query.setParameter("id", associateId);
return query.getResultList();
}
I tried the following, but it just returned all updates in the database:
public List<Update> getUpdates(long associateId) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Update> query = builder.createQuery(Update.class);
Root<Update> fromUpdates = query.from(Update.class);
Root<Associate> fromAssociate = query.from(Associate.class);
Root<Detail> fromDetail = query.from(Detail.class);
Join<Detail, Associate> associateJoin = fromDetail.join("associate");
Join<Detail, Update> updateJoin = fromDetail.join("update");
TypedQuery<Update> typedQuery = em.createQuery(query
.select(fromUpdates)
.where(builder.and(
builder.equal(fromAssociate.get("associateId"), associateId),
builder.equal(fromDetail.get("associate"), associateJoin),
builder.equal(fromDetail.get("update"), updateJoin),
builder.isNull(fromDetail.get("ack_date"))
))
.orderBy(builder.asc(fromUpdates.get("updateId")))
.distinct(true)
);
return typedQuery.getResultList();
}
Can anyone help me? I searched but can't find any example with 3 entities.
Each join takes you from the leftish type parameter to the rightish one. So, the details join of my code (second line) starts from fromUpdates, that is a Path<Update>, and creates something which is behind the scenes also a Path<Detail>. From that, you can build other joins. Try this (code not tested):
Root<Update> fromUpdates = query.from(Update.class);
Join<Update, Detail> details = fromUpdates.join("details");
Join<Detail, Associate> associate = details.join("associate");
List<Predicate> conditions = new ArrayList();
conditions.add(builder.equal(associate.get("associateId"), associateId));
conditions.add(builder.isNull(details.get("ack_date")));
TypedQuery<Update> typedQuery = em.createQuery(query
.select(fromUpdates)
.where(conditions.toArray(new Predicate[] {}))
.orderBy(builder.asc(fromUpdates.get("updateId")))
.distinct(true)
);
For three tables involved.
CriteriaBuilder builder = theEntityManager.getCriteriaBuilder();
CriteriaQuery query1 = builder.createQuery(BasicMemberInfo.class);
Root<Table1> table1 = query1.from(Table1.class);
Root<Table2> table2 = query1.from(Table2.class);
Root<Table3> table3 = query1.from(Table3.class);
List<Predicate> conditions = new ArrayList();
conditions.add(builder.equal(table3.get("Table1").get("memberId"), table1.get("memberId")));
conditions.add(builder.equal(table2.get("tableid").get("memberId"), table1.get("memberId")));
conditions.add(builder.equal(table2.get("indicator"), 'Y'));
conditions.add(builder.equal(table3.get("StatusCd"), "YES"));
TypedQuery<BasicCustInfo> typedQuery = theEntityManager.createQuery(
query1.multiselect(table1.get("memberId"), table2.get("AcctId"))
.where(conditions.toArray(new Predicate[] {}))
);
List<BasicMemberInfo> custList = typedQuery.getResultList();
public class BasicMemberInfo {
String memberId;
String AcctId;
public BasicCustInfo() {
// TODO Auto-generated constructor stub
}
public BasicMemberInfo( BigDecimal memberId,String AcctId ) {
this.memberId = memberId;
this.AcctId = AcctId;
}
public BigDecimal getmemberId() {
return memberId;
}
public void setmemberId(BigDecimal memberId) {
memberId = memberId;
}
public String getAcctId() {
return AcctId;
}
public void setAcctId(String AcctId) {
AcctId = AcctId;
}
}
Checkout this test with even more than three tables . Also use static meta model instead of using direct attribute names.
#Test
#Rollback(false)
#Transactional
public void
fetch() {
CriteriaBuilder cb =
entityManager.getCriteriaBuilder();
CriteriaQuery<Instructor> cq =
cb.createQuery(Instructor.class);
Root<Instructor> root =
cq.from(Instructor.class);
root.join(Instructor_.idProof);
root.join(Instructor_.vehicles);
Join<Instructor, Student> insStuJoin =
root.join(Instructor_.students);
insStuJoin.join(Student_.instructors);
Join<Student, Vehicle> stuVehcileJoin.
= insStuJoin.join(Student_.vehicles);
Join<Vehicle, Document>
vehicleDocumentJoin =
stuVehcileJoin.join(Vehicle_.documents);
DataPrinters.
listDataPrinter.accept.
(queryExecutor.fetchListForCriteriaQuery
(cq.select(root).where
(cb.greaterThan(root.get(Instructor_.id), 2),
cb.in(vehicleDocumentJoin.get
(Document_.name)).value("1")
.value("2").value("3")));
}
Related
I am new to JUnit and testing and am seriously at my wits end with this error:
expected:<null> but was: expected:<null> but was:<Order ID: 1, Customer ID: 1, Customer Name: jordan harrison, Item ID: 1, Item Name: Call of Duty, Quantity: 0, Total Cost: 0.0>
I have no idea why the below test does not work whatsoever. I have looked at other similar questions but they don't help my understanding of the tests at all and left me at a complete loss for how to fix this.
Here's the test:
public class OrderDAOTest {
private final OrderDAO DAO = new OrderDAO();
#Before
public void setup() {
DBUtils.connect();
DBUtils.getInstance().init("src/test/resources/sql-schema.sql", "src/test/resources/sql-data.sql");
}
#Test
public void testRead() {
Long oId = 1L;
Long iId = 1L;
Long cId = 1L;
String iName = "Call of Duty";
double iCost = 25.99;
Item item = new Item(iId, iName, iCost);
Customer customer = new Customer(cId);
CustomerDAO custDao = new CustomerDAO();
customer = custDao.read(customer.getCustomerId());
Order order = new Order();
order.setOrderId(1L);
order.setItem(item);
order.setCustomer(customer);
System.out.println(order);
assertEquals(DAO.read(order.getOrderId()), order);
}
This is the read() method with the orderItemsFromResultSet() method that it returns in the OrderDAO Class:
public Order orderItemsFromResultSet(ResultSet rs) throws SQLException {
Long orderId = rs.getLong("fk_order_id");
Long itemId = rs.getLong("item_id");
String itemName = rs.getString("item_name");
double itemCost = rs.getDouble("item_cost");
Item item = new Item(itemId, itemName, itemCost);
Order order = new Order(item, orderId);
return order;
}
#Override
public Order read(Long id) {
try (Connection connection = DBUtils.getInstance().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT * FROM order_items LEFT OUTER JOIN items ON items.item_id = order_items.fk_item_id WHERE fk_order_id = ?");) {
statement.setLong(1, id);
try (ResultSet resultSet = statement.executeQuery();) {
resultSet.next();
return orderItemsFromResultSet(resultSet);
}
} catch (Exception e) {
LOGGER.debug(e);
LOGGER.error(e.getMessage());
}
return null;
}
I just don't understand it and need help with understanding testing altogether.
You are catching all exceptions and then just returning null. There are two possible places I would debug further.
Check if the Connection is working fine.
Check if the query execution works fine too. Remove the semicolon at the end of the query and it may work.
The logs may help you here.
I have the following repository with 2 custom query methods:
#Repository
public interface CropVarietyNameDao extends JpaRepository<CropVarietyName, Long> {
//https://stackoverflow.com/questions/25362540/like-query-in-spring-jparepository
Set<CropVarietyName> findAllByNameIgnoreCaseContainingOrScientificNameIgnoreCaseContaining(String varietyName, String scientificName);
Set<CropVarietyName> findAllByNameIgnoreCaseStartsWithOrScientificNameIgnoreCaseStartsWith(String varietyName, String scientificName);
}
I need to add an additional condtion, namely parent entity called crop deleted = false.
If i change a method to the following:
findAllByNameIgnoreCaseContainingOrScientificNameIgnoreCaseContainingAndCrop_deletedIsFalse(String varietyName, String scientificName);
Then most likely it will interpret the query as (Name containing OR (scientificNameContaining AND crop_deleted = false), but thats not how i want it.
It needs to be (NameContaining OR scientificNameContaining) AND crop_deleted = false)
My guess is that I have to add the AND crop_deleted part to both parts, but that seems inefficient. How can i write a method thats essentially (NameContaining OR scientificNameContaining) AND crop_deleted = false) without having to use #Query?
You could try JPA Specification -
#Repository
public interface CropVarietyNameDao extends JpaRepository<CropVarietyName, Long>,
JpaSpecificationExecutor<CropVarietyName> {
}
public class CropVarietySpecs {
public static Specification<CropVarietyName> cropPredicate(String varietyName, String sciName, boolean cropStatus) {
return new Specification<CropVarietyName>() {
#Override
public Predicate toPredicate(Root<CropVarietyName> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
Predicate varietyContainingIgnoreCasePredicate = criteriaBuilder.like(criteriaBuilder.lower(root.get("<column_name>")), varietyName.toLowerCase());
Predicate scientificContainingIgnoreCasePredicate = criteriaBuilder.like(criteriaBuilder.lower(root.get("<column_name>")), sciName.toLowerCase());
Predicate cropStatusPredicate = criteriaBuilder.equal(root.get("<column_name>"), cropStatus);
predicates.add(varietyContainingIgnoreCasePredicate);
predicates.add(scientificContainingIgnoreCasePredicate);
criteriaBuilder.or(predicates.toArray(new Predicate[predicates.size()]));
return criteriaBuilder.and(cropStatusPredicate);
}
};
}
}
Then you can call the findAll() method of your repository like -
List<CropVarietyName> entities = cropVarietyNameDao.findAll(CropVarietySpecs.cropPredicate("varietyName", "sciName", false));
I've got an Android Room database setup and followed some tutorials with which I'm trying to extend what I've made. I have gotten a few queries to work but when I try to use a SELECT * WHERE query it returns all the records in the table not just the records with the expected ID.
The Query in the DAO
#Query("SELECT * FROM weapon_table WHERE fighter_id = :fighter_id" )
LiveData<List<Weapon>> getFighterWeapons(long fighter_id);
The Record Object
#Entity(tableName = "weapon_table",
foreignKeys = {
#ForeignKey(entity = com.Database.Fighter.class,
parentColumns = "fighter_id",
childColumns = "fighter_id"),
})
public class Weapon
{
#NonNull
String weapon_name;
#NonNull
#PrimaryKey(autoGenerate = true)
long weapon_id;
#NonNull
long fighter_id;
public Weapon(String weapon_name, long fighter_id)
{
this.weapon_name = weapon_name;
this.fighter_id = fighter_id;
}
public long getWeapon_id()
{
return weapon_id;
}
public void setWeapon_id(long weapon_id)
{
this.weapon_id = weapon_id;
}
public String getWeapon_name()
{
return weapon_name;
}
public void setWeapon_name(String weapon_name)
{
this.weapon_name = weapon_name;
}
public long getFighter_id()
{
return fighter_id;
}
public void setFighter_id(long fighter_id)
{
this.fighter_id = fighter_id;
}
}
When I call the following code all records are printed to the debug console but the current ID is also printed and stays the same in all cases.
public void onClickAddWeapon(View view)
{
List<Weapon> weapons = mWeaponViewModel.getFighterWeapons(fighter.getFighter_id()).getValue();
for(Weapon weapon: weapons )
{
Log.d("WEAPONDEBUG", "WEAPONNAME/ID: " +weapon.getWeapon_name() +'/'+ weapon.getFighter_id() );
Log.d("WEAPONDEBUG", "FIGHTERID: "+ fighter.getFighter_id() );
}
}
Fix was fairly simple , I had followed this google code labs tutorial and when I tried to extend it I forgot to actually assign the result of the query to the list held in the repository file. Thanks for the suggestions they put me in the right direction.
LiveData<List<Weapon>> getFighterWeapons(long fighter_id)
{
mWeaponDao.getFighterWeapons(fighter_id);
return mAllWeapons;
}
replaced with
LiveData<List<Weapon>> getFighterWeapons(long fighter_id)
{
mAllWeapons = mWeaponDao.getFighterWeapons(fighter_id);
return mAllWeapons;
}
your code should be working, but I think that the problem is defining with foreign keys. `
parentColumns = "fighter_id",
childColumns = "fighter_id"
can you remove it and try?
`
Try this
#Query("SELECT * FROM weapon_table WHERE fighter_id IN (:fighter_id)" )
LiveData<List<Weapon>> getFighterWeapons(long fighter_id);
Can you please help me in solving this problem. I am trying to order the results of an criteria query by date, but I'm not getting the results I need.I saved date in String format,how can i order by date using criteria
The code I'm using is:
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
criteria.addOrder(Order.asc("createdDate"));
List<Program> programs=(List<Program>)criteria.list();
return programs;
}
Results are:
01/02/2009
03/01/2009
04/06/2009
05/03/2009
06/12/2008
07/02/2009
Results should be:
06/12/2008
03/01/2009
01/02/2009
07/02/2009
I need to select the date in the format above.
Your help is much appreciated.
You have to call criteria.addOrder(Order.asc("createdDate")); before executing the list method on criteria.
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
criteria.addOrder(Order.asc("createdDate"));
List<Program> programs=(List<Program>)criteria.list();
return programs;
}
EDIT
In your case, if you want to order by String dates, as i mentionned in the comments, this answer is not the proper you can get ( may be turning creationDate into a Date type is the best! for sure).
You can try some code like :
static final String DF = "DD/MM/YYYY";
static final SimpleDateFormat SDF = new SimpleDateFormat(DF);
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
List<Program> =(List<Program>)criteria.list();
boolean asc = true;
programs.sort((a, b) -> {
int comparison = 0;
try {
comparison = SDF.parse(a.getCreatedDate()).compareTo(SDF.parse(b.getCreatedDate()));
} catch (ParseException e) {
// handle it!!
}
return asc ? comparison : (0-comparison);
});
return programs;
}
EDIT 2
If you want to avoid using lambdas, try using this instead :
Collections.sort(programs, new Comparator<Main>() {
#Override
public int compare(Program a, Program b) {
int comparison = 0;
try {
comparison = SDF.parse(a.getCreatedDate()).compareTo(SDF.parse(b.getCreatedDate()));
} catch (ParseException e) {
// handle it!!
}
return asc ? comparison : (0-comparison);
}
});
This is how Implemented it
private List<Users> findUsers(boolean all, int maxResults, int firstResult, SchoolData data) {
EntityManager em = getEntityManager(data.getExternalId());
try {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery();
Root<Users> from = cq.from(Users.class);
cq.orderBy(cb.desc(from.get("dateCreated")));
cq.select(cq.from(Users.class));
Query q = em.createQuery(cq);
if (!all) {
q.setMaxResults(firstResult);
q.setFirstResult(maxResults);
}
return q.getResultList();
} finally {
em.close();
}
}
Criteria are passed to Hibernate and are translated to the certain language. They appear after where clause in the select so they all the limitations must be set before executing query. Although you don't see it, there is a query generated and executed when calling criteria.list().
Try the following:
#Override
public List<Program> getListProgram() {
Session session=sessionFactory.openSession();
Criteria criteria=session.createCriteria(Program.class);
criteria.addOrder(Order.asc("createdDate"));
List<Program> programs=(List<Program>)criteria.list();
return programs;
}
Firstly, dates should be stored as dates, not strings. If you are working with a legacy system, it may not be possible to change this, but I'd argue that it probably would make your life easier to bite the bullet and refactor.
As a string, the sort function is working because 05 comes before 06.
I'd suggest using annotation or mapping, as outlined in this question, to convert your string to a date in the pojo so that it is the correct type at least somewhere in your application.
public class StringToDateConverter implements AttributeConverter<Date, String> {
String convertToDatabaseColumn(Date attribute) { /* Conversion code */ }
Date convertToEntityAttribute(String dbData) { /* Conversion code */ }
}
and
public class MyPojo {
#javax.persistence.Convert(converter = StringToDateConverter.class)
public Date getCreateDate() {
}
}
I've run into a problem while using JPA with Querydsl and Hibernate for data storage management. The sample model is as follows:
#Entity
public class User {
....
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
private Category category;
}
#Entity
public class Category {
..
private String acronym;
#OneToMany(mappedBy = "category")
List<User> userList;
}
In my Spring MVC webapp I have a search form with User parameters and orderBy select. The orderBy select can be either User property or Category property. The orderBy parameters are stored as Map (f.e. {"login" : "adm", {"firstName" : "John"}. The search function receives the search parameters (as string) and the map above with order specification. The simplified code for ordering is as follows:
Map<String, String> orderByMap = new HashMap<String, String>();
orderByMap.put("firstName", "asc");
orderByMap.put("unit.acronym", "desc");
PathBuilder<User> pbu = new PathBuilder<User>(User.class, "user");
....
for (Map.Entry<String, String> order : orderByMap.entrySet())
{
// for simplicity I've omitted asc/desc chooser
query.orderBy(pbu.getString(order.getKey()).asc());
}
The problem starts when I want to introduce sorting by Category's parameter, like {"category.acronym", "desc"}. As explained here, the above code will make querydsl to use cross join with Category table and omitt the Users without Categories, which is not expected behavior.
I know, I have to introduce the left join with Categories and use the alias for the sorting to make it work, hovewer I'm looking for efficient way to do it dynamically. Stripping each String looking for category or any other entity (like "user.category.subcategory.propetry") will introduce a lot of ugly code and I'd rather not do that.
I'd appreciate the help with some more elegant solution.
I added now a protoype of the implementation to the test side of Querydsl https://github.com/mysema/querydsl/issues/582
I will consider a direct integration into Querydsl if this a common use case
public class OrderHelper {
private static final Pattern DOT = Pattern.compile("\\.");
public static PathBuilder<?> join(JPACommonQuery<?> query, PathBuilder<?> builder, Map<String, PathBuilder<?>> joins, String path) {
PathBuilder<?> rv = joins.get(path);
if (rv == null) {
if (path.contains(".")) {
String[] tokens = DOT.split(path);
String[] parent = new String[tokens.length - 1];
System.arraycopy(tokens, 0, parent, 0, tokens.length - 1);
String parentKey = StringUtils.join(parent, ".");
builder = join(query, builder, joins, parentKey);
rv = new PathBuilder(Object.class, StringUtils.join(tokens, "_"));
query.leftJoin((EntityPath)builder.get(tokens[tokens.length - 1]), rv);
} else {
rv = new PathBuilder(Object.class, path);
query.leftJoin((EntityPath)builder.get(path), rv);
}
joins.put(path, rv);
}
return rv;
}
public static void orderBy(JPACommonQuery<?> query, EntityPath<?> entity, List<String> order) {
PathBuilder<?> builder = new PathBuilder(entity.getType(), entity.getMetadata());
Map<String, PathBuilder<?>> joins = Maps.newHashMap();
for (String entry : order) {
String[] tokens = DOT.split(entry);
if (tokens.length > 1) {
String[] parent = new String[tokens.length - 1];
System.arraycopy(tokens, 0, parent, 0, tokens.length - 1);
PathBuilder<?> parentAlias = join(query, builder, joins, StringUtils.join(parent, "."));
query.orderBy(parentAlias.getString(tokens[tokens.length - 1]).asc());
} else {
query.orderBy(builder.getString(tokens[0]).asc());
}
}
}
}