I'm new with JOOQ library and have one thing interesting me so much. I've implemented CRUD service on JOOQ at first and after that I've tried to avoid some duplicate code. For reach that goal I've added JPA repository and also added#Entity annotation to my generated by JOOQ class. And now I still want to use JOOQ for some cases (querying List using filter and sorting and pagination). But something went wrong and now after JOOQ makes a select request I can see nulls in my class's attributes.
I'm getting right count of entities by filter, but class's properties are null after mapping. Is that mapping wrong or I just could't use JOOQ and JPA together for this case?
My abstact class for all entities (as I said, for avoid duplicating code I've refactored some code and now use generics):
#MappedSuperclass
public abstract class AbstractServiceEntity {
private Integer id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
My JPA class (generated by JOOQ):
/**
* This class is generated by jOOQ.
*/
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
#Entity
#Table(schema = "ref", name = "account")
public class Account extends AbstractServiceEntity implements Serializable {
private static final long serialVersionUID = -162537472;
private Integer id;
private Integer transitId;
private Integer partnerId;
private String currencyCode;
private String descr;
private Long inCredit;
private Long balanceLimit;
private Long outCredit;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private Integer transitPartnerId;
public Account() {}
public Account(Account value) {
this.id = value.id;
this.transitId = value.transitId;
this.partnerId = value.partnerId;
this.currencyCode = value.currencyCode;
this.descr = value.descr;
this.inCredit = value.inCredit;
this.balanceLimit = value.balanceLimit;
this.outCredit = value.outCredit;
this.createdAt = value.createdAt;
this.updatedAt = value.updatedAt;
this.transitPartnerId = value.transitPartnerId;
}
public Account(
Integer id,
Integer transitId,
Integer partnerId,
String currencyCode,
String descr,
Long inCredit,
Long balanceLimit,
Long outCredit,
LocalDateTime createdAt,
LocalDateTime updatedAt,
Integer transitPartnerId
) {
this.id = id;
this.transitId = transitId;
this.partnerId = partnerId;
this.currencyCode = currencyCode;
this.descr = descr;
this.inCredit = inCredit;
this.balanceLimit = balanceLimit;
this.outCredit = outCredit;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.transitPartnerId = transitPartnerId;
}
And my method extracting entities from DB:
#Repository
#RequiredArgsConstructor
public class JooqAccountRepository {
private final DSLContext jooq;
public List<Account> findAll(Condition filterCondition, SortField[] sortFields, Integer partnerId, Integer limit, Integer offset) {
return jooq.selectFrom(ACCOUNT)
.where(ACCOUNT.PARTNER_ID.equal(partnerId))
.and(filterCondition)
.orderBy(sortFields)
.limit(limit)
.offset(offset)
.fetchInto(Account.class);
}
public Integer findAccountsCount(Integer partnerId) {
return jooq.selectCount().from(ACCOUNT)
.where(ACCOUNT.PARTNER_ID.equal(partnerId))
.fetchOne(0, Integer.class);
}
}
As a result of my searches - I've made a mistake with annotations in Account class. If you want use these frameworks together, you should use #Column on entity's properties or setting your jooq's codegen plugin in different way)
This resource was usefull for me
Related
Hi Spring and Hibernate experts!
Can any one say if it is possible to use SQL IN-clause in custom #Query in CrudRepository while the Arraylist or set of strings is passed as parameter?
I am relatively new to Spring and do not quite figure out why I get the following Spring error:
"java.lang.IllegalArgumentException: Parameter value [d9a873ed-3f15-4af5-ab1b-9486017e5611] did not match expected type [IoTlite.model.Device (n/a)]"
In this post (JPQL IN clause: Java-Arrays (or Lists, Sets...)?) the subject is discussed pretty closely but I cannot make the suggested solution to work in my case with custom #Query.
My demo repository as part of the spring boot restful application is the following:
#Repository
public interface DeviceRepository extends JpaRepository<Device, Long> {
#Query("SELECT d FROM Device d WHERE d IN (:uuid)")
List<Device> fetchUuids(#Param("uuid") Set<String> uuid);
}
And the model-class is the following:
#Entity
#SequenceGenerator(sequenceName = "device_seq", name = "device_seq_gen", allocationSize = 1)
#JsonIgnoreProperties(ignoreUnknown = true)
public class Device implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "device_seq_gen")
#JsonIgnore
private Integer id;
#Column(unique=true, length=36)
#NotNull
private String uuid = UUID.randomUUID().toString();
#Column(name="name")
private String name;
#JsonInclude(JsonInclude.Include.NON_NULL)
private String description;
#OneToMany(
mappedBy="device",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Sensor> sensors = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
#JsonIgnore
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDeviceUuid() {
return uuid;
}
public void setDeviceUuid(String deviceUuid) {
this.uuid = deviceUuid;
}
public List<Sensor> getSensors() {
return sensors;
}
public void addSensor(Sensor sensor){
sensor.setDevice(this);
sensors.add(sensor);
}
}
An here is the relevant part of the service calling the fetchUuids-custom-method with set-list of strings as parameter (service naturally being called by the relevant restcontroller):
#Service
public class DeviceService implements IDeviceService {
#Autowired
private DeviceRepository deviceRepository;
...
#Override
public List<Device> listDevices(Set<String> clientIds) {
return deviceRepository.fetchUuids(clientIds);
}
...
}
Quick fix
You have WHERE d IN (:uuid) in the custom query. You cannot match d, which is an alias for Device entity with :uuid parameter, which is a collection of Strings.
WHERE d.uuid IN (:uuid) would fix the query - it matches a String with Strings.
What you should do instead
It's rather misleading to name the method fetchUuids and return a list of Device instances. It's also unnecessary to write a custom query to do that. You can benefor from repository method name conventions and let Spring Data Jpa framework generate the query for you:
List<Device> findByUuidIn(Set<String> uuids);
You can write in this way
#Query(value = "select name from teams where name in :names", nativeQuery = true)
List<String> getNames(#Param("names") String[] names);
and call the function in service and pass an array of String as arguments.like this
String[] names = {"testing team","development team"};
List<String> teamtest = teamRepository.getNames(names);
Yes is possible to using collection in JPA query parameters.
Your query is wrong, it should be like this:
#Query("SELECT d FROM Device d WHERE d.uuid IN :uuid")
I found this code snippet while looking at other projects and I was wondering on the benefit of having a public interface within a java pojo. Why should you use it as seen in the below example. Is the below use considered a best practice? Is there a catch with using an interface liek this?
#Data
#NoArgsConstructor
#Entity
#Table(name = "customer_mapping", schema = "billing")
public class CustomerMapping {
public interface createCustomerMapping {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonProperty("customerMappingId")
private Integer id;
#Column(length=10)
#NotNull(groups = { createCustomerMapping.class })
#NotEmpty(groups = { createCustomerMapping.class })
#Size(groups = { createCustomerMapping.class }, min = 4, max = 4)
private String issuerCode;
#Column(length=10)
#NotNull(groups = { createCustomerMapping.class })
#NotEmpty(groups = { createCustomerMapping.class })
private String offerCode;
#NotNull
private Long customerId;
#CreationTimestamp
private LocalDateTime createdTs;
private String createdBy;
#UpdateTimestamp
private LocalDateTime updatedTs;
private String updatedBy;
public CustomerMapping(String offerCode) {
this.offerCode = offerCode;
}
public CustomerMapping(String issuerCode, String offerCode) {
this.issuerCode = issuerCode;
this.offerCode = offerCode;
}
public CustomerMapping(String issuerCode, String offerCode, Long customerId) {
this.issuerCode = issuerCode;
this.offerCode = offerCode;
this.customerId = customerId;
}
}
From what I know an interface is used to define a contract so I'm familiar with the implementation in using it via a library of code or if two teams needs to co-operate. But in the above example, its unclear to me.
I try select data from the table by a filter with Spring Data JPA Specification I think what my implementation is correct, But it doesn't work. Help me please understand my mistake and fix my example.
I have very strange SQL query in log :
select phone0_.id as id1_0_, phone0_.note as note2_0_, phone0_.number as number3_0_, phone0_.operator_login as operator4_0_, phone0_.operator_pass as operator5_0_, phone0_.operator_name as operator6_0_, phone0_.operator_url as operator7_0_, phone0_.reg_date as reg_date8_0_, phone0_.status as status9_0_ from phone phone0_ where 0=1 limit ?
In the end: where 0=1 it's crash my mind. Where did that come from?
Here I fill CriteriaBuilder if filter field not null. I expect to get correctly built Specification object and send it to findAll(Specifications.where(specification), Pageable p) method. But something incorrect.
My repo and specification impl:
public interface PhoneRepository extends CrudRepository<Phone, Integer>, JpaRepository<Phone, Integer>, JpaSpecificationExecutor<Phone> {
class PhoneSpecification implements Specification<Phone> {
private final #NonNull PhoneService.PhoneFilter filter;
public PhoneSpecification(#NonNull PhoneService.PhoneFilter filter) {
this.filter = filter;
}
#Override
public Predicate toPredicate(Root<Phone> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.disjunction();
if (nonNull(filter.getId())) {
cb.disjunction().getExpressions().add(cb.equal(root.get("id"), filter.getId()));
}
if (nonNull(filter.getNote())) {
cb.disjunction().getExpressions().add(cb.like(root.get("note"), filter.getNote()));
}
if (nonNull(filter.getNumber())) {
cb.disjunction().getExpressions().add(cb.like(root.get("number"), filter.getNumber()));
}
if (nonNull(filter.getStatus())) {
cb.disjunction().getExpressions().add(cb.like(root.get("status"), filter.getStatus()));
}
if (nonNull(filter.getOpName())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorName"), filter.getOpName()));
}
if (nonNull(filter.getOpLogin())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccLogin"), filter.getOpLogin()));
}
if (nonNull(filter.getOpPassword())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccPassword"), filter.getOpPassword()));
}
if (nonNull(filter.getRegFrom()) && nonNull(filter.getRegTo())) {
cb.disjunction().getExpressions().add(cb.between(root.get("regDate"), filter.getRegFrom(), filter.getRegTo()));
}
return predicate;
}
}
}
This is service level:
#Service
public class PhoneService {
#Autowired
private PhoneRepository phoneRepository;
public Phone get(int id) {
Phone phone = phoneRepository.findOne(id);
return nonNull(phone) ? phone : new Phone();
}
public Page<Phone> list(#NonNull PhoneFilter filter) {
PhoneSpecification specification = new PhoneSpecification(filter);
return phoneRepository.findAll(Specifications.where(specification), filter.getPageable());
}
#Data
public static class PhoneFilter {
private Pageable pageable;
private Integer id;
private Timestamp regFrom;
private Timestamp regTo;
private String number;
private String opLogin;
private String opPassword;
private String opName;
private String status;
private String note;
}
}
And entity
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "phone")
#ToString(exclude = {"accounts"})
#EqualsAndHashCode(exclude = {"accounts"})
public class Phone {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToMany(mappedBy = "phone", cascade = CascadeType.DETACH)
private Collection<SocialAccount> accounts;
#Column(name = "reg_date")
private Timestamp regDate;
#Column(name = "number")
private String number;
#Column(name = "operator_url")
private String operatorUrl;
#Column(name = "operator_login")
private String operatorAccLogin;
#Column(name = "operator_pass")
private String operatorAccPassword;
#Column(name = "operator_name")
private String operatorName;
#Column(name = "status")
private String status;
#Column(name = "note")
private String note;
}
I find the mistake.
Method CriteriaBuilder.disjunction() this is factory and each time when I call him I got new Predicate object.
This implementation CriteriaBuilderImpl:
public Predicate disjunction() {
return new CompoundPredicate(this, BooleanOperator.OR);
}
Be careful with it.
I am using spring jpa repository with hibernate to save entites to my oracle database. How I can get the next value of my oracle database sequence using Spring-Hibernate?
This is my Event class :
#Entity
public class Event {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Long seriesId;
private String description;
public Event() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getSeriesId() {
return seriesId;
}
public void setSeriesId(Long seriesId) {
this.seriesId = seriesId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
I need to get the next value of the sequence once for the all event series in the event resolver.
public class EventResolver {
#Autowired
private EventRepository eventRepository;
public void createSeriesOfEvents(List<EventAPI> eventsToCreate){
Long seriesId = null; // TODO: Get the series id from database sequence
for (EventAPI currEvent : eventsToCreate){
Event newEvent = new Event();
newEvent.setDescription(currEvent.description);
newEvent.setSeriesId(seriesId);
eventRepository.save(newEvent);
}
}
}
Thanks for any kind of help..
Finally I Solved my problem in the Spring way, All you need is to add a native query in the JpaRepository like this:
public interface EventRepository extends JpaRepository<Event, Long> {
#Query(value = "SELECT seq_name.nextval FROM dual", nativeQuery =
true)
Long getNextSeriesId();
With Spring 5, you can use one of their built-in classes for this task like OracleSequenceMaxValueIncrementer
See all the available options in this package: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/support/incrementer/package-summary.html
Annotate your id property like so:
#Id
#GeneratedValue(generator = "idSequence")
#SequenceGenerator(schema = "MYORASCHEMA", name = "idSequence", sequenceName = "MY_ORACLE_SEQ_NAME", allocationSize = 1)
#Column(name="ID")
private Long id;
You can use this approach in JPA:
Query q = em.createNativeQuery("select seq_name.nextval from dual");
return (Long)q.getSingleResult();
I'm working with Spring, hibernate and MySql but I have some problem with seralization of query result.
First in my entity I added #JsonManagedReference on Set structure (#OneToMany side) and #JsonBackReference on single object reference (#ManyToOne side) and it works but I wasn't be able to retrieve all needed information (for example #ManyToOne reference).
So i swapping #JsonBackReference on set structure and #JsonManagedReference on single object but I retrieve
No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.model.tablesField.TableUI["data"]->java.util.ArrayList[0]->com.domain.Car["carType"]->com.domain.CarType_$$_jvst744_f["handler"])
I tried also with #JsonIgnore on Set structure but it doesn't work for the same issues.
This is my spring configuration
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
// properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
properties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
properties.put("hibernate.enable_lazy_load_no_trans",true);
return properties;
and this is part of one of my several entities:
/**
* Car generated by hbm2java
*/
#Entity
#Table(name = "car", catalog = "ATS")
public class Car implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idCar;
#JsonManagedReference
private CarType carType;
#JsonManagedReference
private Fleet fleet;
private String id;
private int initialKm;
private String carChassis;
private String note;
#JsonBackReference
private Set<Acquisition> acquisitions = new HashSet<Acquisition>(0);
public Car() {
}
public Car(CarType carType, Fleet fleet, int initialKm, String carChassis) {
this.carType = carType;
this.fleet = fleet;
this.initialKm = initialKm;
this.carChassis = carChassis;
}
public Car(CarType carType, Fleet fleet, String id, int initialKm, String carChassis, String note,
Set<Acquisition> acquisitions) {
this.carType = carType;
this.fleet = fleet;
this.id = id;
this.initialKm = initialKm;
this.carChassis = carChassis;
this.note = note;
this.acquisitions = acquisitions;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id_car", unique = true, nullable = false)
public Integer getIdCar() {
return this.idCar;
}
public void setIdCar(Integer idCar) {
this.idCar = idCar;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_carType", nullable = false)
public CarType getCarType() {
return this.carType;
}
public void setCarType(CarType carType) {
this.carType = carType;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_fleet", nullable = false)
public Fleet getFleet() {
return this.fleet;
}
public void setFleet(Fleet fleet) {
this.fleet = fleet;
}
#Column(name = "id", length = 5)
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
#Column(name = "initialKm", nullable = false)
public int getInitialKm() {
return this.initialKm;
}
public void setInitialKm(int initialKm) {
this.initialKm = initialKm;
}
#Column(name = "carChassis", nullable = false, length = 20)
public String getCarChassis() {
return this.carChassis;
}
public void setCarChassis(String carChassis) {
this.carChassis = carChassis;
}
#Column(name = "note", length = 100)
public String getNote() {
return this.note;
}
public void setNote(String note) {
this.note = note;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
public Set<Acquisition> getAcquisitions() {
return this.acquisitions;
}
public void setAcquisitions(Set<Acquisition> acquisitions) {
this.acquisitions = acquisitions;
}
}
one method that uses the query:
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public #ResponseBody TableUI getCars(#PathVariable int idFleet) {
TableUI ajaxCall=new TableUI();
try {
ajaxCall.setData(fleetAndCarService.findCarsByIdFleet(idFleet));
return ajaxCall;
} catch (QueryException e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in FleetAndCarControllerImpl::addCar :" + errorResponse.getStacktrace());
return ajaxCall;
}
}
two class for the query:
public interface DefRdiRepository extends JpaRepository<DefRdi, Integer>{
//#Query("SELECT CASE WHEN COUNT(c) > 0 THEN true ELSE false END FROM DefRdi c WHERE c.parName = ?1 AND c.description= ?2")
//Boolean existsByParNameAndDescription(String parName, String description);
//Query method of spring, I put findBy and then the key of research
DefRdi findByParNameAndDescription(String parName, String description);
}
public interface CarRepository extends JpaRepository<Car, Integer>, CarRepositoryCustom {
//Query method of spring, I put findBy and then the key of research
List<Car> findByFleetIdFleet(int idFleet);
}
Where is my error? I don't want Set object but only the single reference. The problem is only when I serialize. Thanks
UPDATE:
I use #JSonIgnore on all set collectionts and Eager instead lazy ad all works fine, but is there a way to retrieve all the information only when I want, for example having two different query?
So it doesn't work
#Override
#Transactional
public List<Car> findByFleetIdFleet(int idFleet) {
List<Car> carList= carRepository.findByFleetIdFleet(idFleet);
for (Car car:carList){
Hibernate.initialize(car.getCarType());
Hibernate.initialize(car.getFleet());
}
return carList;
// return carRepository.findByFleetIdFleet(idFleet);
}
All collections need to be fetched eagerly when loading them from data base, in order to get serialized by Spring. Make sure you fetch them eagerly (e.g. FetchMode.JOIN). You could also swap #JsonManagedReference from wanted fields with #JsonIgnore to black listed fields, Spring automatically serialises every field without annotation.
Update:
Changing the data repository to something like that should work, I am not sure it compiles, but I think you will get the point:
#EntityGraph(value = "some.entity.graph", type = EntityGraph.EntityGraphType.FETCH)
#Query(
value = "SELECT c FROM Car c INNER JOIN FETCH c.acquisitions WHERE c.id = :idFleet"
)
public interface CarRepository extends JpaRepository<Car, Integer>, CarRepositoryCustom {
//Query method of spring, I put findBy and then the key of research
List<Car> findByFleetIdFleet(int idFleet);
}
For more information look at this post and read the official documentation.
Workaround:
There seems to be a workaround, however fetching those collections eager like shown above should have a positive performance impact, since there is no need for loading proxies afterwards. Also no open transactions are needed at controller level.