How to update JPA/Hibernate entities with Apache Camel - spring-boot

I have a spring boot project with apache camel (Using maven dependencies: camel-spring-boot-starter, camel-jpa-starter, camel-endpointdsl).
There are the following 3 entities:
#Entity
#Table(name = RawDataDelivery.TABLE_NAME)
#BatchSize(size = 10)
public class RawDataDelivery extends PersistentObjectWithCreationDate {
protected static final String TABLE_NAME = "raw_data_delivery";
private static final String COLUMN_CONFIGURATION_ID = "configuration_id";
private static final String COLUMN_SCOPED_CALCULATED = "scopes_calculated";
#Column(nullable = false, name = COLUMN_SCOPED_CALCULATED)
private boolean scopesCalculated;
#OneToMany(mappedBy = "rawDataDelivery", fetch = FetchType.LAZY)
private Set<RawDataFile> files = new HashSet<>();
#CollectionTable(name = "processed_scopes_per_delivery")
#ElementCollection(targetClass = String.class)
private Set<String> processedScopes = new HashSet<>();
// Getter/Setter
}
#Entity
#Table(name = RawDataFile.TABLE_NAME)
#BatchSize(size = 100)
public class RawDataFile extends PersistentObjectWithCreationDate {
protected static final String TABLE_NAME = "raw_data_files";
private static final String COLUMN_CONFIGURATION_ID = "configuration_id";
private static final String COLUMN_RAW_DATA_DELIVERY_ID = "raw_data_delivery_id";
private static final String COLUMN_PARENT_ID = "parent_file_id";
private static final String COLUMN_IDENTIFIER = "identifier";
private static final String COLUMN_CONTENT = "content";
private static final String COLUMN_FILE_SIZE_IN_BYTES = "file_size_in_bytes";
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = COLUMN_RAW_DATA_DELIVERY_ID)
private RawDataDelivery rawDataDelivery;
#Column(name = COLUMN_IDENTIFIER, nullable = false)
private String identifier;
#Lob
#Column(name = COLUMN_CONTENT, nullable = true)
private Blob content;
#Column(name = COLUMN_FILE_SIZE_IN_BYTES, nullable = false)
private long fileSizeInBytes;
// Getter/Setter
}
#Entity
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#Table(name = RawDataRecord.TABLE_NAME, uniqueConstraints = ...)
public class RawDataRecord extends PersistentObjectWithCreationDate {
public static final String TABLE_NAME = "raw_data_records";
static final String COLUMN_RAW_DATA_FILE_ID = "raw_data_file_id";
static final String COLUMN_INDEX = "index";
static final String COLUMN_CONTENT = "content";
static final String COLUMN_HASHCODE = "hashcode";
static final String COLUMN_SCOPE = "scope";
#ManyToOne(optional = false)
#JoinColumn(name = COLUMN_RAW_DATA_FILE_ID)
private RawDataFile rawDataFile;
#Column(name = COLUMN_INDEX, nullable = false)
private long index;
#Lob
#Type(type = "jsonb")
#Column(name = COLUMN_CONTENT, nullable = false, columnDefinition = "jsonb")
private String content;
#Column(name = COLUMN_HASHCODE, nullable = false)
private String hashCode;
#Column(name = COLUMN_SCOPE, nullable = true)
private String scope;
}
What I try to do is to build a route with apache camel which selects all deliveries having the flag "scopesCalculated" == false and calculate/update the scope variable of all records attached to the files of this deliveries. This should happen in one database transaction. If all scopes are updated I want to set the scopesCalculated flag to true and commit the changes to the database (in my case postgresql).
What I have so far is this:
String r3RouteId = ...;
var dataSource3 = jpa(RawDataDelivery.class.getName())
.lockModeType(LockModeType.NONE)
.delay(60).timeUnit(TimeUnit.SECONDS)
.consumeDelete(false)
.query("select rdd from RawDataDelivery rdd where rdd.scopesCalculated is false and rdd.configuration.id = " + configuration.getId())
;
from(dataSource3)
.routeId(r3RouteId)
.routeDescription(configuration.getName())
.messageHistory()
.transacted()
.process(exchange -> {
RawDataDelivery rawDataDelivery = exchange.getIn().getBody(RawDataDelivery.class);
rawDataDelivery.setScopesCalculated(true);
})
.transform(new Expression() {
#Override
public <T> T evaluate(Exchange exchange, Class<T> type) {
RawDataDelivery rawDataDelivery = exchange.getIn().getBody(RawDataDelivery.class);
return (T)rawDataDelivery.getFiles();
}
})
.split(bodyAs(Iterator.class)).streaming()
.transform(new Expression() {
#Override
public <T> T evaluate(Exchange exchange, Class<T> type) {
RawDataFile rawDataFile = exchange.getIn().getBody(RawDataFile.class);
// rawDataRecordJpaRepository is an autowired interface by spring with the following method:
// #Lock(value = LockModeType.NONE)
// Stream<RawDataRecord> findByRawDataFile(RawDataFile rawDataFile);
// we may have many records per file (100k and more), so we don't want to keep them all in memory.
// instead we try to stream the resultset and aggregate them by 500 partitions for processing
return (T)rawDataRecordJpaRepository.findByRawDataFile(rawDataFile);
}
})
.split(bodyAs(Iterator.class)).streaming()
.aggregate(constant("all"), new GroupedBodyAggregationStrategy())
.completionSize(500)
.completionTimeout(TimeUnit.SECONDS.toMillis(5))
.process(exchange -> {
List<RawDataRecord> rawDataRecords = exchange.getIn().getBody(List.class);
for (RawDataRecord rawDataRecord : rawDataRecords) {
rawDataRecord.setScope("abc");
}
})
;
Basically this is working, but I have the problem that the records of the last partition will not be updated. In my example I have 43782 records but only 43500 are updated. 282 remain with scope == null.
I really don't understand the JPA transaction and session management of camel and I can't find some examples on how to update JPA/Hibernate entities with camel (without using SQL component).
I already tried some solutions but none of them are working. Most attempts end with "EntityManager/Session closed", "no transaction is in progress" or "Batch update failed. Expected result 1 but was 0", ...
I tried the following:
to set jpa(...).joinTransaction(false).advanced().sharedEntityManager(true)
use .enrich(jpa(RawDataRecord.class.getName()).query("select rec from RawDataRecord rec where rawDataFile = ${body}")) instead of .transform(...) with JPA repository for the records
using hibernate session from camel headers to update/save/flush entities: "Session session = exchange.getIn().getHeader(JpaConstants.ENTITY_MANAGER, Session.class);"
try to update over new jpa component at the end of the route:
.split(bodyAs(Iterator.class)).streaming()
.to(jpa(RawDataRecord.class.getName()).usePersist(false).flushOnSend(false))
Do you have any other ideas / recommendations?

Related

Question on persisting entity with Bidirectional #ManyToOne relationship using Mapstruct

This is my first time implementing #ManyToOne relationship using JPA/Mapstruct/Spring boot. I am running into the below exception: (NOTE: Using generic names as I am unable to share all the details)
java.sql.SQLIntegrityConstraintViolationException: Column 'A_ID' cannot be null for class B when I try to persist A.
Below are the relevant details. Can you help me understand what is the mistake I am making here? I have spent few hours debugging this and reading the posts without success yet.
#Mapper(componentModel="spring", uses= {BMapper.class}, collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface AMapper {
#Mapping(target = "aId", source="id")
#Mapping(target = "aName", source = "name")
ADTO toDTO(final A a);
#Mapping(target = "id", ignore=true)
#Mapping(target = "name", source = "aName")
A toEntity(final ADTO aDTO);
#AfterMapping
default void setBSet(A a, #MappingTarget aDTO dto) {
for(B b : a.getBs())
b.setA(a);
}
}
#Mapper(componentModel="spring", uses= {CMapper.class}, injectionStrategy = InjectionStrategy.CONSTRUCTOR )
public interface BMapper {
#Mapping(target = "bId", source="id")
#Mapping(target = "aName", ignore=true)
BDTO toDTO(final B b);
#Mapping(target = "id", ignore=true)
#Mapping(target = "a", ignore=true)
B toEntity(final BDTO bDTO);
Set<B> bDtoToBSetEntity(Set<BDTO> set);
Set<BDTO> bSetEntityToBDto(Set<B> set);
}
Below are the class definitions of ADTO and BDTO
public class ADTO implements Serializable {
private static final long serialVersionUID = 8307772637314390585L;
private Long aId;
private String aName;
private LocalDate startDate;
private LocalDate endDate;
private Set<BDTO> bs = new HashSet<>();
// Getters / Setters here
public void addToBs(BDTO b) {
if(b != null) {
bs.add(b);
}
}
// hashCode/equals/toString methods here...
}
public class BDTO implements Serializable {
private static final long serialVersionUID = 2562084231749296452L;
private Long bId;
private String name;
private LocalDate startDate;
private LocalDate endDate;
private String aName;
// getters / setters go here..
// hashCode/equals/toString methods here...
}
Below are the class definitions of entity classes, particularly pay attention to class B where the exception is related to.
#Entity
#Table(name = "TABLEB", uniqueConstraints = {
#UniqueConstraint(columnNames = "ID")})
public class B implements Serializable {
private static final long serialVersionUID = 1407209531508355406L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Enumerated(EnumType.STRING)
#Column(name = "NAME", nullable = false)
private String name;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "A_ID", referencedColumnName="ID")
private A a;
#Column(name = "START_DATE", unique = false, nullable = false)
private LocalDate startDate;
#Column(name = "END_DATE", unique = false, nullable = false)
private LocalDate endDate;
}
#Entity
#Table(name = "TABLEA", uniqueConstraints = {
#UniqueConstraint(columnNames = "ID")})
public class A implements Serializable {
private static final long serialVersionUID = 6926335188960198569L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Long id;
#Column(name = "NAME", unique = false, nullable = false, length = 100)
private String name;
#OneToMany(mappedBy="a"/*, fetch = FetchType.EAGER*/, cascade = {CascadeType.ALL})
private Set<B> bs = new HashSet<>();
#Column(name = "START_DATE", unique = false, nullable = false)
private LocalDate startDate;
#Column(name = "END_DATE", unique = false, nullable = false)
private LocalDate endDate;
}
I am just calling save method of ADao class that persists using entity Manager.

Spring Data Jpa One To One mapping with where clause

I have two tables and I need OneToOne mapping with where clause.
select * from person_details inner join address_details
on address_details.pid=person_details.pid AND person_details.exist_flag = 'Y' AND address_details.address_exist_flag = 'Y'
Table 1
public class PersonDetails {
#Id
private String pid;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "exist_flag")
private String existFlag;
#OneToOne(mappedBy = "personDetails", cascade = CascadeType.ALL)
#Where(clause = "addressExistFlag = 'Y'")
private AddressDetails addressDetails;
}
Table 2
#Data
#NoArgsConstructor
#Entity
#Table(name = "address_details")
public class AddressDetails {
#Id
private String pid;
private String street;
#Column(name = "address_exist_flag")
private String addressExistFlag;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "pid", insertable = false, updatable = false)
private PersonDetails personDetails;
}
I need data to be fetched if both addressExistFlag = 'Y' and existFlag = 'Y'.
With current scenario If I am trying to fetch data via spring batch read repository as below, only existFlag = 'Y' is considered. Is it because of incorrect mapping or the way I have used in spring batch
ReadRepository looks like below
public interface PersonDetailsRepository extends JpaRepository<PersonDetails, String> {
Page<PersonDetails> findByExistFlag(String existFlag, Pageable pageable);
}
Spring batch read repository looks like below
#Bean
RepositoryItemReader<PersonDetails> personDetailsItemReader() {
Map<String, Sort.Direction> sort = new HashMap<>();
sort.put("ExistFlag", Sort.Direction.ASC);
return new RepositoryItemReaderBuilder<PersonDetails>()
.repository(personDetailsRepository)
.methodName("findByExistFlag")
.arguments("Y")
.sorts(sort)
.name("personDetailsItemReader")
.build();
}
You are only querying for existsFlag.
You have to add the other Flag too:
public interface PersonDetailsRepository extends JpaRepository<PersonDetails, String> {
Page<PersonDetails> findByExistFlagAndAddressDetailsAddressExistFlag(
String existFlag, String addressExistFlag, Pageable pageable);
}
#Bean
RepositoryItemReader<PersonDetails> personDetailsItemReader() {
Map<String, Sort.Direction> sort = new HashMap<>();
sort.put("ExistFlag", Sort.Direction.ASC);
return new RepositoryItemReaderBuilder<PersonDetails>()
.repository(personDetailsRepository)
.methodName("findByExistFlagAndAddressDetailsAddressExistFlag")
.arguments("Y", "Y")
.sorts(sort)
.name("personDetailsItemReader")
.build();
}

Spring data JPA entity change not being persisted

I have a Spring data entity (using JPA w/ Hibernate and MySQL) defined as such:
#Entity
#Table(name = "dataset")
public class Dataset {
#Id
#GenericGenerator(name = "generator", strategy = "increment")
#GeneratedValue(generator = "generator")
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "guid", nullable = false)
private String guid;
#Column(name = "size", nullable = false)
private Long size;
#Column(name = "create_time", nullable = false)
private Date createTime;
#OneToOne(optional = false)
#JoinColumn(name = "created_by")
private User createdBy;
#Column(name = "active", nullable = false)
private boolean active;
#Column(name = "orig_source", nullable = false)
private String origSource;
#Column(name = "orig_source_type", nullable = false)
private String origSourceType;
#Column(name = "orig_source_org", nullable = false)
private String origSourceOrg;
#Column(name = "uri", nullable = false)
private String uri;
#Column(name = "mimetype", nullable = false)
private String mimetype;
#Column(name = "registration_state", nullable = false)
private int registrationState;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "dataset_id")
#JsonManagedReference
private List<DatasetFile> datasetFiles;
I have the following repository for this entity:
public interface DatasetRepo extends JpaRepository<Dataset, Long> {
#Query("SELECT CASE WHEN COUNT(p) > 0 THEN 'true' ELSE 'false' END FROM Dataset p WHERE p.uri = ?1 and p.registrationState>0")
public Boolean existsByURI(String location);
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c where a.registrationState>0")
public List<Dataset> getAll(Pageable pageable);
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c WHERE a.registrationState>0")
public List<Dataset> findAll();
#Query("SELECT a FROM Dataset a LEFT JOIN FETCH a.datasetFiles c where a.guid= ?1")
public Dataset findByGuid(String guid);
}
Now - In a controller, I am fetching a dataset, updating one of its attributes and I would be expecting that attribute change to be flushed to the DB, but it never is.
#RequestMapping(value = "/storeDataset", method = RequestMethod.GET)
public #ResponseBody
WebServiceReturn storeDataset(
#RequestParam(value = "dsGUID", required = true) String datasetGUID,
#RequestParam(value = "stType", required = true) String stType) {
WebServiceReturn wsr = null;
logger.info("stType: '" + stType + "'");
if (!stType.equals("MongoDB") && !stType.equals("Hive") && !stType.equals("HDFS")) {
wsr = getFatalWebServiceReturn("Invalid Storage type '" + stType + "'");
} else if (stType.equals("MongoDB")) {
/* Here is where I'm reading entity from Repository */
Dataset dataset = datasetRepo.findByGuid(datasetGUID);
if (dataset != null) {
MongoLoader mongoLoader = new MongoLoader();
boolean success = mongoLoader.loadMongoDB(dataset);
logger.info("Success: " + success);
if (success) {
/* Here is where I update entity attribute value, this is never flushed to DB */
dataset.setRegistrationState(1);
}
wsr = getWebServiceReturn(success ? 0 : -1, "Successfully loaded dataset files into " + stType + " storage", "Failed to load dataset files into " + stType + " storage");
}
}
return wsr;
}
Thank you
You need to annotate the method of request mapping with #Transactional.
Why? If you want to modify an object in memory and then it is updated transparently in the database you need do it inside an active transaction.
Don't forget you're using JPA (spring-data is using JPA) and if you want your Entity will be in a managed state you need an active transaction.
See:
http://www.objectdb.com/java/jpa/persistence/update
Transparent Update Once an entity object is retrieved from the
database (no matter which way) it can simply be modified in memory
from inside an active transaction:
Employee employee = em.find(Employee.class, 1);
em.getTransaction().begin();
employee.setNickname("Joe the Plumber");
em.getTransaction().commit();

Spring Data JPA Specification Predicate for a #OneToMany Collection not working

For background:
I have built a module that captures a list of a historical events that occur against an asset over its life and using JPA specifications using spring-data-jpa with hibernate to run the dynamic query using the JPA SpecificationExecutor interface. I have the following historical event JPA object with a many to one asset this historical event is directly against and other associated assets this historical event is also associated with defined in a many-to-many relationship. I am trying to write a JPA Specification predicate that pulls all historical events for a given asset that the asset is either directly against or associated too by using the includeAssociations flag in the predicate. When I try to execute the predicate I am not getting the correct results when I have the includeAssociations flag set to true. I would expect it would by default return at a minimum all the historical events they are directly as if the includeAssociations was false plus any ones they are indirectly associated with. I need help figuring out why this predicate is not returning back what I would expect. Any help is much appreciated!
Here is my Historical Event JPA object:
#Entity
#Table(name = "LC_HIST_EVENT_TAB")
public class HistoricalEvent extends BaseEntity implements Comparable<HistoricalEvent>, Serializable
{
private static final long serialVersionUID = 1L;
#ManyToOne(targetEntity = Asset.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "ASSET_ID")
private Asset asset;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = Asset.class)
#JoinTable(name = "LC_HIST_EVENT_ASSETS", joinColumns =
{
#JoinColumn(name = "HIST_EVENT_ID", referencedColumnName = "id")
}, inverseJoinColumns =
{
#JoinColumn(name = "ASSET_ID", referencedColumnName = "id")
}, uniqueConstraints =
{
#UniqueConstraint(columnNames =
{
"HIST_EVENT_ID", "ASSET_ID"
})
})
#BatchSize(size=10)
#OrderBy("partCatalogItem.partID, serialNumber ASC")
private Set<Asset> associatedAssets;
#Column(name = "START_DATE", nullable = true)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar startDate;
#Column(name = "END_DATE", nullable = true)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar endDate;
}
JPA Metamodel for Historical Event:
#StaticMetamodel(HistoricalEvent.class)
public class HistoricalEvent_ extends BaseEntity_
{
public static volatile SingularAttribute<HistoricalEvent, Asset> asset;
public static volatile SetAttribute<HistoricalEvent, Asset> associatedAssets;
public static volatile SingularAttribute<HistoricalEvent, Calendar> startDate;
public static volatile SingularAttribute<HistoricalEvent, Calendar> endDate;
public static volatile SingularAttribute<HistoricalEvent, String> type;
public static volatile SingularAttribute<HistoricalEvent, String> description;
public static volatile SingularAttribute<HistoricalEvent, HistoricalEvent> triggeringEvent;
public static volatile SetAttribute<HistoricalEvent, HistoricalEvent> associatedEvents;
public static volatile MapAttribute<HistoricalEvent, String, HistoricalEventMap> data;
}
Here is my Asset JPA Object:
#Entity
#Table(name = "LC_ASSET_TAB")
public class Asset extends BaseEntity implements Comparable<Asset>, Serializable
{
private static final long serialVersionUID = 1L;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = PartCatalog.class)
#JoinColumn(name = "PART_CATALOG_ID", nullable = false)
private PartCatalog partCatalogItem;
#Column(name = "SERIAL_NO", nullable = false)
private String serialNumber;
#Column(name = "DATE_INTO_SERVICE", nullable = false)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar dateIntoService;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "asset", targetEntity = AssetMap.class)
#MapKey(name = "fieldName")
#BatchSize(size=25)
private Map<String, AssetMap> data;
}
Asset Metamodel:
#StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
public static volatile SingularAttribute<PartCatalog, String> partID;
public static volatile SingularAttribute<PartCatalog, String> nsn;
public static volatile SingularAttribute<PartCatalog, String> description;
public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}
Here is my Part Catalog JPA object:
#Entity
#Table(name = "LC_PART_CATALOG_TAB")
public class PartCatalog extends BaseEntity implements Comparable<PartCatalog>, Serializable
{
private static final long serialVersionUID = 1L;
#Column(name = "PART_ID", length=100, nullable = false)
private String partID;
#Column(name = "NSN", length=100, nullable = true)
private String nsn;
#Column(name = "DESCRIPTION", length=250, nullable = false)
private String description;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "partCatalogItem", targetEntity = PartCatalogMap.class)
#MapKey(name = "fieldName")
private Map<String, PartCatalogMap> data;
}
Part Catalog Metamodel:
#StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
public static volatile SingularAttribute<PartCatalog, String> partID;
public static volatile SingularAttribute<PartCatalog, String> nsn;
public static volatile SingularAttribute<PartCatalog, String> description;
public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}
Specification Predicate for returning historical events by a given Part Number and Serial Number:
PROBLEM: If includeAssociations is false, it returns fine however soon as it is true, it returns the wrong list of associations and never returns any results from the events the asset is directly tied too like if the includeAssociations was false. This is where I need help how to best write the criteria builder query to properly pull the data.
These are the two JPQL queries I am trying to combine into the Predicate using the Criteria API:
Normal:
#Query("SELECT he FROM HistoricalEvent he WHERE he.asset.partCatalogItem.partID =:partID AND he.asset.serialNumber =:serialNumber " +
"AND he.startDate >:startDate AND he.endDate <:endDate")
Association:
#Query("SELECT he FROM HistoricalEvent he INNER JOIN he.associatedAssets associated WHERE associated.partCatalogItem.partID =:partID AND associated.serialNumber =:serialNumber " +
"AND he.startDate >:startDate AND he.endDate <:endDate");
/**
* Creates a specification used to find historical events by a given asset part number and serial
* parameter.
*
* #param partID - part identifier
* #Param serialNumber
* #return Historical Event Specification
*/
public static Specification<HistoricalEvent> hasPartAndSerial(final String partID, final String serialNumber, final Boolean includeAssociations)
{
return new Specification<HistoricalEvent>() {
#Override
public Predicate toPredicate(Root<HistoricalEvent> historicalEventRoot,
CriteriaQuery<?> query, CriteriaBuilder cb) {
if (partID == null || partID == "")
{
return null;
}
if(serialNumber == null || serialNumber =="")
{
return null;
}
Path<Asset> assetOnEvent = historicalEventRoot.get(HistoricalEvent_.asset);
Path<PartCatalog> partCatalogItem = assetOnEvent.get(Asset_.partCatalogItem);
Expression<String> partIdToMatch = partCatalogItem.get(PartCatalog_.partID);
Expression<String> serialToMatch = assetOnEvent.get(Asset_.serialNumber);
if(includeAssociations)
{
SetJoin<HistoricalEvent, Asset> assetsAssociatedToEvent = historicalEventRoot.join(HistoricalEvent_.associatedAssets);
Path<PartCatalog> partCatalogItemFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.partCatalogItem);
Expression<String> partIdToMatchFromAssociatedAsset = partCatalogItemFromAssociatedAsset.get(PartCatalog_.partID);
Expression<String> serialToMatchFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.serialNumber);
return cb.or(cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase())),
cb.and(cb.equal(cb.lower(partIdToMatchFromAssociatedAsset), partID.toLowerCase()), cb.equal(cb.lower(serialToMatchFromAssociatedAsset), serialNumber.toLowerCase())));
}
else
{
return cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase()));
}
}
};
}
Finally I am calling this to find the historical events:
#Override
public Page<HistoricalEvent> getByCriteria(String type, String partID,
String serialNumber, Calendar startDate, Calendar endDate,
Boolean includeAssociations, Integer pageIndex, Integer recordsPerPage)
{
LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Searching historical event repository for type of " + type + " , part id of " + partID +
" , serial number of " + serialNumber + " , start date of " + startDate + " , end date of " + endDate + ", include associations flag of " + includeAssociations
+ " , pageIndex " + pageIndex + " and records per page of " + recordsPerPage);
Page<HistoricalEvent> requestedPage = historicalEventRepository.findAll(Specifications
.where(HistoricalEventSpecifications.hasType(type))
.and(HistoricalEventSpecifications.greaterThanOrEqualToStartDate(startDate))
.and(HistoricalEventSpecifications.lessThanOrEqualToEndDate(endDate))
.and(HistoricalEventSpecifications.hasPartAndSerial(partID, serialNumber, includeAssociations)),
DatabaseServicePagingUtil.getHistoricalEventPagingSpecification(pageIndex, recordsPerPage));
LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Found " + requestedPage.getTotalElements() + " that will comprise " + requestedPage.getTotalPages() + " pages of content.");
return requestedPage;
} UPDATE: i have been able to get the specification if the historical event was either directly or indirectly associated working however using the following Predicate 1 = cb.equals(cb.lower(partIDToMatch, partID.toLowercase()); Predicate2 = cb.equals(cb.lower(serialToMatch), serialNumber.toLowercase(); Predicate3 = cb.or(Predicate1, Predicate2 ); Predicate4 = cb.equals(cb.lower(partIDToMatchFromAssociatedAsset), partIDToMatch.toLowercase()); Predicate5 = cb.equals(cb.lower(serialNumberFromAssociatedAsset), serialNumberToMatch.toLowercase()); Predicate6 = cb.and(Predicate4, Predicate5); Predicate7 = cb.or(Predicate3,Predicate6); When i return Predicate I only get results matching Predicate6 not either one as i would expect. I want it to pull events where either predicate condition returns a record. Each predicate returns the right data but when i use the cb.or it doesnt combine results as i would expect. What am I missing?
You have to start printing the query and parameters value that are bean generated, just enable this properties.
After that you have to analyze your query and make some tests with different combinations to check your jpa specification are falling.
There is no magic way to do that and it's hard and painful :(
Good look

Tapestry 5.4 URL rewriting and SEO URLs

i'm writing a web application in tapestry.
In my application i want to use friendly urls. Now i'm able to to render the url page like this:
http://localhost:8080/page/page-name
wat i want to do is to render URLs like this:
http://localhost:8080/page-name
All pages are stored on a Postgresql DB.
I'm currently using Tapestry 5.4-beta16 and i've already read the tapestry documentation:
http://tapestry.apache.org/url-rewriting.html
http://blog.tapestry5.de/index.php/2010/09/06/new-url-rewriting-api/
Now, this is the Class for list all pages stored on the DB (Only for test)
public class Pages {
#Inject
private Session session;
#Property
List<it.garuti.tapestrycms.entities.Pagine> pagine;
#Property
private it.garuti.tapestrycms.entities.Pagine pagina;
void setupRender() {
pagine = session.createCriteria(it.garuti.tapestrycms.entities.Pagine.class).list();
}
}
And this is the class for show the page content:
public class Page {
#Property
private Page page;
#Inject
private Logger logger;
#Inject
private Session session;
#Inject
Request request;
#InjectPage
Index index;
#Property
private String slug;
void onActivate(String slug) {
this.slug = slug;
}
Object onActivate() {
if (session.createCriteria(Pagine.class)
.add(Restrictions.eq("slug", slug)).uniqueResult() == null)
return new HttpError(404, "Resource not found");
return null;
}
String onPassivate() {
return slug;
}
void setupRender() {
page = (Pages) session.createCriteria(Pages.class)
.add(Restrictions.eq("slug", slug)).uniqueResult();
}
}
And finally the Entity for Pages:
#Entity
#Table(name = "pages", schema = "public")
public class Pages implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "pagine_seq")
#SequenceGenerator( name= "pagine_seq", sequenceName = "pagine_id_seq")
#Column(name = "id", unique = true, nullable = false)
private long id;
#Column(name = "titolo", nullable = false, length = 60, unique = true)
private String titolo;
#Column(name = "slug", nullable = false, length = 60, unique = true)
private String slug;
#Column(name = "contenuto", nullable = false, columnDefinition = "TEXT")
private String contenuto;
#Column(name = "data_creazione", nullable = false)
private Date dataCreazione;
#Column(name = "data_modifica")
private Date dataModifica;
#Column(name = "stato", nullable = false)
private String stato;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "utente_id", nullable = false)
private Users user;
getter and setter...
....
}
Tank You
Lorenzo
There's a special case in tapestry when your page class has the name Index it will not include the page name in the URL. So if you rename the class Page to Index I think you'll achieve what you want without requiring any explicit url rewriting

Resources