Is there any way to avoid usage of BooleanJunction while migrating to Hibernate 6?
My (poor) understanding is that Hibernate 6 abandoned Quer DSL and is using JPA criteria instead. Code sample is just about 10 % of code that I have to cover all search cases and it uses more that shown here, meaning BooleanJunction is needed for this to work.
import javax.persistence.EntityManager;
import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.query.dsl.QueryContextBuilder;
import org.hibernate.search.query.dsl.EntityContext;
import org.hibernate.search.query.dsl.QueryBuilder;
// ...
#Autowired
private final EntityManager entityManager;
private static final Pattern SPACES =
Pattern.compile("\\s+", Pattern.UNICODE_CHARACTER_CLASS);
private static final int MIN_TOKEN_LENGTH = 2;
#AllArgsConstructor #Getter
public enum SearchField {
// there are 20 entries; here is one just to show
NAME(SearchField.NAME_STR, 20.0f);
private String fieldName;
private float boostFactor;
}
// ...
final FullTextEntityManager fullTextEntityManager =
Search.getFullTextEntityManager(this.entityManager);
final SearchFactory searchFactory =
fullTextEntityManager.getSearchFactory();
final QueryContextBuilder queryContextBuilder =
searchFactory.buildQueryBuilder();
final EntityContext entityContext =
queryContextBuilder.forEntity(clazz);
final QueryBuilder queryBuilder =
entityContext.get();
// ...
final String searchTerm = "some search term";
final String[] searchTerms =
Arrays.stream(SPACES.split(searchTerm))
.map(StringUtils::trimToNull)
.filter(Objects::nonNull)
.filter(s -> s.length() >= IndexingUtil.MIN_TOKEN_LENGTH)
.toArray(String[]::new);
final List<Query> queries = new LinkedList<>();
for (int i = 0; i < searchTerms.length; ++i) {
final String word = searchTerms[i];
queries.add(
queryBuilder
.bool()
.should(
QueryForField.q(
SearchField.NAME,
word,
queryBuilder
)
)
.boostedTo(SearchField.NAME.getBoostFactor()
);
);
}
// more queries are added, not only query by `name`
final BooleanJunction<?> master = queryBuilder.bool();
queries.stream().filter(Objects::nonNull).forEach(master::must);
final Query q = master.createQuery();
Related
I am creating a Spring web application that queries SPARQL endpoints. As a requirement, I'm supposed to save the query and the result for later viewing and editing. So far I have created some entities (QueryInfo, Result, Endpoint) that I use to save the information entered about the Query and the Result. However I'm having trouble with saving the actual results themselves
public static List<String> getSelectQueryResult(QueryInfo queryInfo){
Endpoint endpoint = queryInfo.getEndpoint();
Query query = QueryFactory.create(queryInfo.getContent());
List<String> subjectStrings = query.getResultVars();
List<String> list = new ArrayList();
RDFConnection conn = RDFConnectionFactory.connect(endpoint.getUrl());
QueryExecution qExec = conn.query(queryInfo.getContent()) ; //SELECT DISTINCT ?s where { [] a ?s } LIMIT 100
ResultSet rs = qExec.execSelect() ;
while (rs.hasNext()) {
QuerySolution qs = rs.next();
System.out.println("qs: "+qs);
RDFNode rn = qs.get(subjectStrings.get(0)) ;
System.out.print(qs.varNames());
if(rn!= null) {
if (rn.isLiteral()) {
Literal literal = qs.getLiteral(subjectStrings.get(0));
list.add(literal.toString());
} else if (rn.isURIResource()) {
Resource subject = qs.getResource(subjectStrings.get(0));
System.out.println("Subject: " + subject.toString());
list.add(subject.toString());
}
}
}
return list;
}
My Result entity looks like this:
#Entity #Data #Table(schema = "sparql_tool") public class Result {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 10485760)
private String content;
#OneToOne
#JoinColumn(name = "query_info_id",referencedColumnName = "id")
private QueryInfo queryInfo;
#Column(length = 10485760)
#Convert(converter = StringListConverter.class)
private List<String> contentList;
public Result() {
}
public Result(String content, QueryInfo queryInfo, List<String> list) {
this.content = content;
this.queryInfo = queryInfo;
this.contentList=list;
}
}
I used to save the actual results in the List contentList attribute. However, this only works when the query has only one result variable. If I have multiple result variables I have a table instead of a list. What is the best way to save this result in DB?
I'm working with an SQL DB if that is relevant. Thank you so much in advance!
I've created a custom repository to filter a list of entities from the db. Below is a basic initialization of the implemented class (entities are reduced to letters for clarification):
#Repository
public class CustomXRepositoryImpl implements CustomXRepository {
private final EntityManager em;
private final CriteriaBuilder builder;
private final CriteriaQuery<X> query;
private final Root<X> root;
#Autowired
public CustomXRepositoryImpl(EntityManager em) {
this.em = em;
builder = em.getCriteriaBuilder();
query = builder.createQuery(X.class);
root = query.from(X.class);
}
}
and the method I implemented:
#Override
public List<X> filter(FilterParams params) {
List<Predicate> restrictions = new ArrayList<>();
// some if-statements ...
if (params.getY() != null) {
Expression<?> path1 = root.get("a").get("b").get("y");
Expression<?> path2 = root.get("c").get("d").get("y");
Predicate p1 = builder.equal(path1, params.getY());
Predicate p2 = builder.equal(path2, params.getY());
// restrictions.add(p1); // 3 results for Y = 4
// restrictions.add(p2); // no result for Y = 4
restrictions.add(builder.or(p1, p2)); // no result
}
// more if-statements ...
query.where(restrictions.toArray(new Predicate[0]));
return em.createQuery(query).getResultList();
}
What makes me confused is the fact that builder.or(p1, p2) returns no result, even though p1 is satisfied. I also find the CriteriaBuilder#or documentation (Returns a disjunction of the given restriction predicates) very ambiguous.
Can anyone explain this behavior?
How to apply like with in clause in spring boot jpa.Below is the class.
#Table(name="media")
public class Media {
#NotBlank
private String url;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#ElementCollection
private Set<String> tagList = new HashSet<String>();
public Media(String urlString) {
this.url = urlString ;
}
}
For example if there is a row with tagList ["mentos", "hurre"] and i want to search for "men" or ["men","hu"] this row should come ?
I have defined below method but it return a row only if string completely match.
Set<Media> findByTagListIn(List<String> tagList);
You need to query by specification like below:
//MediaRepository
import org.springframework.data.jpa.domain.Specification;
...
List<Media> findAll(Specification<Media> spec);
and create that specification in service class.
//MediaService
List<Media> findMediaByTags(List<String> tags){
Specification<Media> specification = (Specification<Media>) (root, query, criteriaBuilder) -> {
Predicate predicate = criteriaBuilder.conjunction();
for (String tag : tags) {
predicate = criteriaBuilder.and(predicate,
criteriaBuilder.isMember(tag, root.get("tags")));
}
return predicate;
};
return mediaRepository.findAll(specification);
}
Trying to achieve
fetch all the data from main table and all the corresponding child record with activeYn != N.
This is parent entity
#Entity
#Table(name="IR_TB_INCIDENT_HDR")
public class IncidentHdr implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="IR_TB_INCIDENT_HDR_INCIDENTID_GENERATOR", sequenceName="IR_SEQ_INCIDENT_ID",allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="IR_TB_INCIDENT_HDR_INCIDENTID_GENERATOR")
#Column(name="INCIDENT_ID")
private long incidentId;
#Column(name="PATIENT_ID")
private Long patientId;
#OneToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE, CascadeType.REMOVE}, fetch = FetchType.LAZY, mappedBy="incidentHdr")
private Set<Attachments> attachments;
....
//other child entities
}
This is the child entity
#Entity
#Table(name="IR_TB_ATTACHMENTS")
public class Attachments implements Serializable {
private Long attachmentId;
private IncidentHdr incidentHdr;
private boolean activeYn;
}
Here we are genegating the custom query, we are appending only one condition here.
public IncidentHdr findIncidentDetailForId(Long incidentId) throws BusinessException {
StringBuilder query = null;
IncidentHdr incidentHdr = null;
StringBuilder incidentDetailQuery = null;
Query q = null;
Map < String, Object > parameters = new HashMap < String, Object > ();
List < String > criteria = new ArrayList < String > ();
try {
incidentDetailQuery = new StringBuilder();
query = new StringBuilder();
query.append(ReposJPQL.GET_INCIDENTS_DETAIL);
criteria.add("inc.incidentId = :incidentId ");
parameters.put("incidentId", incidentId);
if (criteria.size() > 0) {
for (int i = 0; i < criteria.size(); i++) {
incidentDetailQuery.append(" AND ");
incidentDetailQuery.append(criteria.get(i));
}
}
query.append(incidentDetailQuery);
q = em.createQuery(query.toString());
for (Entry < String, Object > entry: parameters.entrySet()) {
q.setParameter(entry.getKey(), entry.getValue());
}
incidentHdr = (IncidentHdr) q.getSingleResult();
}catch(IllegalArgumentException | IllegalStateException | DataAccessException | EntityNotFoundException e) {
logger.error(e.getMessage());
throw new BusinessException(e);
}
return incidentHdr;
}
ReposJPQL, Here defined the query with activeYn condition.
public interface ReposJPQL {
public String GET_INCIDENTS_DETAIL = "SELECT inc "
+ " FROM IncidentHdr inc left join inc.attachments att WHERE att.activeYn <> 'N' ";
}
Even though the records are present it return "org.springframework.dao.EmptyResultDataAccessException: No entity found for query; nested exception is javax.persistence.NoResultException: No entity found for query"
error
Or is there any other way to achieve this ? #Where(clause=...) option is pure hibernate so cant use that.
I'm developing a spring boot project which uses spring batch scheduler to read data after 2 sec's and send it to the message broker (Activemq) which works fine with hardcoded fixed delay.
However, i'm now trying to read the #Scheduled(fixedDelay) from database rather hard coded but looks like nothing is working out. I can see the expression contains 10 secs but scheduler doesn't start
#Service
public class QuoteService implements ApplicationListener<BrokerAvailabilityEvent> {
private static Log logger = LogFactory.getLog(QuoteService.class);
private final MessageSendingOperations<String> messagingTemplate;
private final StockQuoteGenerator quoteGenerator = new StockQuoteGenerator();
private AtomicBoolean brokerAvailable = new AtomicBoolean();
private ReadCronExpressionDataService readCronExpressionDataService;
private int expression;
#Autowired
public QuoteService(MessageSendingOperations<String> messagingTemplate,ReadCronExpressionDataService readCronExpressionDataService) {
this.messagingTemplate = messagingTemplate;
this.readCronExpressionDataService=readCronExpressionDataService;
expression = readCronExpressionDataService.readData();
}
#Scheduled(fixedDelay=expression) //#Scheduled(fixedDelay=2000)
public void sendQuotes() {
for (Quote quote : this.quoteGenerator.generateQuotes()) {
if (logger.isTraceEnabled()) {
logger.trace("Sending quote " + quote);
}
if (this.brokerAvailable.get()) {
this.messagingTemplate.convertAndSend("/topic/price.stock." + quote.getTicker(), quote);
}
}
}
private static class StockQuoteGenerator {
private static final MathContext mathContext = new MathContext(2);
private final Random random = new Random();
private final Map<String, String> prices = new ConcurrentHashMap<>();
public StockQuoteGenerator() {
this.prices.put("CTXS", "24.30");
this.prices.put("DELL", "13.03");
this.prices.put("EMC", "24.13");
this.prices.put("GOOG", "893.49");
this.prices.put("MSFT", "34.21");
this.prices.put("ORCL", "31.22");
this.prices.put("RHT", "48.30");
this.prices.put("VMW", "66.98");
}
public Set<Quote> generateQuotes() {
Set<Quote> quotes = new HashSet<>();
for (String ticker : this.prices.keySet()) {
BigDecimal price = getPrice(ticker);
quotes.add(new Quote(ticker, price));
}
return quotes;
}
private BigDecimal getPrice(String ticker) {
BigDecimal seedPrice = new BigDecimal(this.prices.get(ticker), mathContext);
double range = seedPrice.multiply(new BigDecimal(0.02)).doubleValue();
BigDecimal priceChange = new BigDecimal(String.valueOf(this.random.nextDouble() * range), mathContext);
return seedPrice.add(priceChange);
}
}
}
Any idea?