I have a problem referring to this query. On Postgres, this query executes without errors. On JAVA, it throws the following exception:
javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
cause
org.postgresql.util.PSQLException: ERROR: operator does not exist: uuid = bytea
Note: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Position: 404
What I do?
My Method:
public List<CivilRecord> dashboardSearch(CivilRecordSearch civilRecordSearch)
throws MessageException {
SearchValidation.validateDashboardSearch(civilRecordSearch);
List<CivilRecord> l = new ArrayList<>();
try {
StringBuilder query = new StringBuilder();
// query.append("select
// c.id_civil_record\\:\\:text,c.nm_request,c.nm_rg,c.tx_name,c.dt_register,c.bl_priority
// ");
query.append("select c.id_civil_record,c.nm_request,c.nm_rg,c.tx_name,c.dt_register,c.bl_priority ");
query.append("from sc_civil.tb_civil_record c ");
query.append("inner join sc_civil.tb_workflow_record w ");
query.append("on w.id_civil_record = c.id_civil_record ");
query.append("left join sc_civil.tb_lock l ");
query.append("on l.id_record = c.id_civil_record ");
query.append("where c.id_site = :idSite ");
if (civilRecordSearch.getPriority() == null || civilRecordSearch.getPriority().equals(false))
query.append("and c.bl_priority = :priority ");
query.append("and c.bl_canceled = :canceled ");
query.append("and w.id_type_workflow = :idTypeWorkflow ");
query.append("and w.id_type_status_workflow = :idTypeStatusWorkflow ");
query.append("and (l is null or l.id_user = :idUser) ");
if (!StringUtils.isEmpty(civilRecordSearch.getName()))
query.append("and c.tx_name ilike :name ");
if (!StringUtils.isEmpty(civilRecordSearch.getRg()))
query.append("and c.nm_rg like :rg ");
if (civilRecordSearch.getRequestNumber() != null)
query.append("and c.nm_request = :request ");
query.append("order by c.bl_priority desc, c.dt_register ");
Query q = em.createNativeQuery(query.toString());
q.setParameter("idSite", civilRecordSearch.getSite().getId());
if (civilRecordSearch.getPriority() == null || civilRecordSearch.getPriority().equals(false))
q.setParameter("priority", false);
q.setParameter("idTypeWorkflow", civilRecordSearch.getTypeworkflow().getId());
q.setParameter("idTypeStatusWorkflow", civilRecordSearch.getTypestatusworkflow().getId());
q.setParameter("idUser", civilRecordSearch.getIdUser());
q.setParameter("canceled", false);
if (!StringUtils.isEmpty(civilRecordSearch.getName()))
q.setParameter("name", "%" + civilRecordSearch.getName() + "%");
if (civilRecordSearch.getRequestNumber() != null)
q.setParameter("request", civilRecordSearch.getRequestNumber());
if (!StringUtils.isEmpty(civilRecordSearch.getRg()))
q.setParameter("rg", civilRecordSearch.getRg());
q.setMaxResults(maxResult);
List<Object []> lo = q.getResultList();
em.clear();
for (Object [] o : lo) {
CivilRecord c = new CivilRecord();
c.setIdCivilRecord(UUID.fromString((String) o[0]));
c.setRequest((Long) o[1]);
c.setRg((String) o[2]);
c.setName((String) o[3]);
c.setWorkflowRecords(findStatus(c.getIdCivilRecord()));
l.add(c);
}
return l;
}
catch (Exception e) {
log.severe(e.getMessage());
throw e;
}
}
My Class CivilRecordSearch:
import java.io.Serializable;
import java.util.UUID;
public class CivilRecordSearch implements Serializable {
private static final long serialVersionUID = 1701325902333490974L;
// site, prioridade, tipo wf e status wf
private Site site;
private Boolean priority;
private TypeWorkflow typeworkflow;
private TypeStatusWorkflow typestatusworkflow;
private Integer amount;
private UUID idUser;
private String name;
private String rg;
private Long requestNumber;
public Site getSite() {
return site;
}
public void setSite(Site site) {
this.site = site;
}
public Boolean getPriority() {
return priority;
}
public void setPriority(Boolean priority) {
this.priority = priority;
}
public TypeWorkflow getTypeworkflow() {
return typeworkflow;
}
public void setTypeworkflow(TypeWorkflow typeworkflow) {
this.typeworkflow = typeworkflow;
}
public TypeStatusWorkflow getTypestatusworkflow() {
return typestatusworkflow;
}
public void setTypeStatusWorkflow(TypeStatusWorkflow typestatusworkflow) {
this.typestatusworkflow = typestatusworkflow;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
public Integer getAmount() {
return amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public UUID getIdUser() {
return idUser;
}
public void setIdUser(UUID idUser) {
this.idUser = idUser;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRg() {
return rg;
}
public void setRg(String rg) {
this.rg = rg;
}
public Long getRequestNumber() {
return requestNumber;
}
public void setRequestNumber(Long requestNumber) {
this.requestNumber = requestNumber;
}
}
I solve my problem with this form:
I utilized the command CAST for my UUID fields
public List<CivilRecord> dashboardSearch(CivilRecordSearch civilRecordSearch)
throws MessageException {
SearchValidation.validateDashboardSearch(civilRecordSearch);
List<CivilRecord> l = new ArrayList<>();
try {
StringBuilder query = new StringBuilder();
//query.append("select c.id_civil_record\\:\\:text,c.nm_request,c.nm_rg,c.tx_name,c.dt_register,c.bl_priority ");
query.append("select CAST(c.id_civil_record as text),c.nm_request,c.nm_rg,c.tx_name,c.dt_register,c.bl_priority ");
query.append("from sc_civil.tb_civil_record c ");
query.append("inner join sc_civil.tb_workflow_record w ");
query.append("on w.id_civil_record = c.id_civil_record ");
query.append("left join sc_civil.tb_lock l ");
query.append("on l.id_record = c.id_civil_record ");
query.append("where c.id_site = :idSite ");
if (civilRecordSearch.getPriority() == null || civilRecordSearch.getPriority().equals(false))
query.append("and c.bl_priority = :priority ");
query.append("and c.bl_canceled = :canceled ");
query.append("and w.id_type_workflow = :idTypeWorkflow ");
query.append("and w.id_type_status_workflow = :idTypeStatusWorkflow ");
query.append("and (l is null or l.id_user = CAST(:idUser AS uuid)) ");
if (!StringUtils.isEmpty(civilRecordSearch.getName()))
query.append("and c.tx_name ilike :name ");
if (!StringUtils.isEmpty(civilRecordSearch.getRg()))
query.append("and c.nm_rg like :rg ");
if (civilRecordSearch.getRequestNumber() != null)
query.append("and c.nm_request = :request ");
query.append("order by c.bl_priority desc, c.dt_register ");
Query q = em.createNativeQuery(query.toString());
q.setParameter("idSite", civilRecordSearch.getSite().getId());
if (civilRecordSearch.getPriority() == null || civilRecordSearch.getPriority().equals(false))
q.setParameter("priority", false);
q.setParameter("idTypeWorkflow", civilRecordSearch.getTypeworkflow().getId());
q.setParameter("idTypeStatusWorkflow", civilRecordSearch.getTypestatusworkflow().getId());
q.setParameter("idUser", civilRecordSearch.getIdUser().toString());
q.setParameter("canceled", false);
if (!StringUtils.isEmpty(civilRecordSearch.getName()))
q.setParameter("name","%" + civilRecordSearch.getName() + "%");
if (civilRecordSearch.getRequestNumber() != null)
q.setParameter("request", civilRecordSearch.getRequestNumber());
if (!StringUtils.isEmpty(civilRecordSearch.getRg()))
q.setParameter("rg", civilRecordSearch.getRg());
q.setMaxResults(maxResult);
List<Object[]> lo = q.getResultList();
em.clear();
for(Object[] o : lo){
CivilRecord c = new CivilRecord();
c.setIdCivilRecord(UUID.fromString((String)o[0]));
c.setRequest(((BigInteger)o[1]).longValue());
c.setRg((String)o[2]);
c.setName((String)o[3]);
c.setRegister((Date)o[4]);
c.setPriority(TypeYesNo.getByKey(((Boolean)o[5]).booleanValue()));
c.setWorkflowRecords(findStatus(c.getIdCivilRecord()));
l.add(c);
}
return l;
} catch (Exception e) {
log.severe(e.getMessage());
throw e;
}
}
Problem:
Hibernate should map the java UUID type to the postgress uuid type. However, if hibernate does not know how to map this, it will just try to serialize the object, resulting in a byte[]. Of course, this just moves the issue to the database level. Values of the uuid postgress type cannot just be compared with byte array type.
PSQLException: ERROR: operator does not exist: uuid = bytea
Possible cause:
I encountered this issue when migrating from Spring Boot 1.x to Spring Boot 2.3.0. In Spring Boot 1.x it was sufficient for me to mark my id fields with #Id and to make them of java type UUID.
Quick and dirty solutions:
A possible solution, is to explicitly state the PSQL type for the id field.
#Type(type="org.hibernate.type.PostgresUUIDType")
#Id
private UUID id;
A better solution, would be to define a system-wide replacement. You could put this declaration on any class or on a package. Defining it just once somewhere impacts all declarations of UUID actually.
#TypeDef(name="postgres-uuid",
defaultForType = UUID.class,
typeClass = PostgresUUIDType.class)
Real solution:
Take a look in your log file, and you may see something like this. Double check the version of this dialect, and see if it matches with the one which you defined in your property files:
Dialect - HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL81Dialect
In that case, be aware that the following property is outdated:
hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
Some of the hibernate properties now need to have a spring.jpa.properties prefix. So, in this case, the new property path should be spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect.
Which is the point where everything starts to make sense. This dialect does all required type definitions for you.
The same error can be caused by accidentally passing null as an argument, which is then regarded as a bytea type, making the cast to uuid impossible.
So, before going into the solutions above, I would recommend checking one's business logic and quickly activating the hibernate binding parameters logs in application.yml:
logging:
level:
org:
hibernate:
type: TRACE
The faulty argument then becomes apparent, as in my case:
....
binding parameter [6] as [VARBINARY] - [null]
...
To also see the sql queries, add:
spring:
jpa:
show-sql: true
Similar response to Robson Silveira.
Pay attention to the last bit of the query string.
public Account getAccount(String name, String password, String tenantId) {
Query q = em.createNativeQuery(
"SELECT a.id, a.name, a.password, a.email FROM accounts a WHERE name = :name and password = :password and tenant_fk = CAST(:tenant_fk AS uuid)"
);
q.setParameter("name", name);
q.setParameter("password", password);
q.setParameter("tenant_fk", tenantId);
List<Object[]> results = q.getResultList();
if (results.size() == 0) {
return null;
}
return mapFromObject(results.get(0));
}
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)
I have the following problem using derby embedded DB in my application:
I changed a program that works with Mysql data base to use a Derby embedded data base because the client want it like this due to internet problems, everything works fine, but when i want to make a search using a range of dates the console shows the next error:
2018-12-10 17:26:18.920 INFO 107044 --- [nio-7009-exec-3]
C.wDatos_ControlTransferencias_Servicios : /transferencias/consultar
2018-12-10 17:26:18.970 WARN 107044 --- [nio-7009-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 30000, SQLState: 22007
2018-12-10 17:26:18.970 ERROR 107044 --- [nio-7009-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : The syntax of the string representation of a date/time value is incorrect.
2018-12-10 17:26:19.009 ERROR 107044 --- [nio-7009-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.persistence.PersistenceException: org.hibernate.exception.DataException: could not execute query] with root cause
org.apache.derby.iapi.error.StandardException: The syntax of the string representation of a date/time value is incorrect.
Like i said when it was working with MySql it works perfectly.
the query that i use is this:
#SuppressWarnings("unchecked")
#RequestMapping(value = "/transferencias/consultatodos", method = RequestMethod.POST, produces = "application/json; charset=utf-8")
#CrossOrigin
#RequestScope
Collection<Transferencias> transferenciasConsultastodos(#RequestBody Consulta conf) throws JsonProcessingException {
List<Transferencias> transl = new ArrayList<>();
log.info("/transferencias/consultar");
String Query = "Select trans from Transferencias trans";
if (conf.getFecha1() != null && conf.getFecha2() != null) {
Query = Query + " WHERE trans.fecha >= '" + conf.getFecha1() + "' AND trans.fecha<= '"+conf.getFecha2()+"'";
if (conf.getBanco() != null) {
Query = Query + " AND trans.banco = '" + conf.getBanco() +"'";
}
if (conf.getBeneficiario() != null) {
Query = Query + " AND trans.beneficiario = '" + conf.getBeneficiario() +"'";
}
if (conf.getTipo() != null) {
Query = Query + " AND trans.tipo = '" + conf.getTipo() +"'";
}
}
else {
if (conf.getBanco() != null) {
Query = Query + " WHERE trans.banco = '" + conf.getBanco() +"'";
if (conf.getBeneficiario() != null) {
Query = Query + " AND trans.beneficiario = '" + conf.getBeneficiario() +"'";
}
if (conf.getTipo() != null) {
Query = Query + " AND trans.tipo = '" + conf.getTipo() +"'";
}
}
else {
if (conf.getBeneficiario() != null) {
Query = Query + " WHERE trans.beneficiario = '" + conf.getBeneficiario() +"'";
if (conf.getTipo() != null) {
Query = Query + " AND trans.tipo = '" + conf.getTipo() +"'";
}
}
else {
if (conf.getTipo() != null) {
Query = Query + " WHERE trans.tipo = '" + conf.getTipo() +"'";
}
}
}
}
transl = em.createQuery(Query).getResultList();
return transl;
}
When i do the search using other parameters it works perfectly, the problem is with the date.
The entity:
package ControlTransferencias.wDatos;
import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
* #author juan.finol
*/
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Transferencias {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private Long referencia;
private String beneficiario;
private String banco;
private Date fecha;
private String notaAdicional;
private String descripcion;
private Long monto;
private String tipo;
public String getTipo() {
return tipo;
}
public void setTipo(String tipo) {
this.tipo = tipo;
}
public Long getReferencia() {
return referencia;
}
public void setReferencia(Long referencia) {
this.referencia = referencia;
}
public String getBeneficiario() {
return beneficiario;
}
public void setBeneficiario(String beneficiario) {
this.beneficiario = beneficiario;
}
public String getBanco() {
return banco;
}
public void setBanco(String banco) {
this.banco = banco;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDescripcion() {
return descripcion;
}
public void setDescripcion(String descripcion) {
this.descripcion = descripcion;
}
public Date getFecha() {
return fecha;
}
public void setFecha(Date fecha) {
this.fecha = fecha;
}
public Long getMonto() {
return monto;
}
public void setMonto(Long monto) {
this.monto = monto;
}
public String getNotaAdicional() {
return notaAdicional;
}
public void setNotaAdicional(String notaAdicional) {
this.notaAdicional = notaAdicional;
}
}
Another thing that i notice was on the front end, when i make the search it shows the date like this: 1544414400000 and when i was using MySql it shows a dd-mm-yyyy format.
Note: the back end was made in spring boot.
Indeed, there is no standardization of the default string format for date and time datatypes across database systems, so issuing SQL queries with dates in literal string format is quite non-portable.
A much more portable way is to use the JDBC PreparedStatement object, and its setDate() method, to get your date values into your query.
Using PreparedStatement and its parameter substitution features also has the excellent benefit of making your program much less susceptible to SQL Injection vulnerabilities.
Look here for excellent documentation: https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html
#phantomsnake here is some code to format date's
DateTimeFormatter mon = DateTimeFormatter.ofPattern("MMMM");
DateTimeFormatter day = DateTimeFormatter.ofPattern("d");
DateTimeFormatter year = DateTimeFormatter.ofPattern("yyyy");
DateTimeFormatter dayOFwk = DateTimeFormatter.ofPattern("EEEE");
DateTimeFormatter mdwd = DateTimeFormatter.ofPattern("MMMM d EEEE");
LocalDateTime now = LocalDateTime.now();
System.out.println(mon.format(now));
System.out.println(day.format(now));
System.out.println(year.format(now));
System.out.println(dayOFwk.format(now));
System.out.println(mdwd.format(now));
}
Currently, I'm using the standard way to map enums with Hibernate, e.g.
#Entity
public class Job {
#Enumerated(EnumType.STRING)
protected State state;
}
public enum State{
NEW,OLD;
}
Now the requirements changed, and I have to create a table State, which contains all the valid values of my enum as String constants. Thus Job must refer to the State table. I don't have to migrate legacy data.
What options do I have to map this with JPA / Hibernate?
Is it possible, to let Hibernate create the State table with values ( 1->"new", 2->"old").
during DDL generation?
in ended up with a solution, which generates the DDL including enum constants and foreign key constraints.
e.g.
#Entity
public enum MyEnum{
#EnumValue
private String name;
#Id
private int id;
}
#Entity
public class MyEntity {
#EnumReference
protected MyEnum myEnum;
}
is sufficient with the following MetadataContributor (/src/main/resources/META-INF/services/org.hibernate.boot.spi.MetadataContributor):
public class EnumConstantsMetadataContributor implements MetadataContributor {
private final static Logger LOG = LoggerFactory.getLogger(EnumConstantsMetadataContributor.class);
private final static List<String> ENABLED_ON = Arrays.asList("validate", "update", "create", "create-drop");
private final static Integer DEFAULT_VARCHAR_SIZE = 255;
private final static Identifier DEFAULT_COLUMN_NAME = Identifier.toIdentifier("enum_constant", false);
#Override
public void contribute(InFlightMetadataCollector metadataCollector, IndexView jandexIndex) {
if (shouldRun(metadataCollector)) {
addEnumsAsTableConstantsAndFkConstraint(metadataCollector);
}
}
private boolean shouldRun(InFlightMetadataCollector metadataCollector) {
StandardServiceRegistry serviceRegistry = metadataCollector.getMetadataBuildingOptions().getServiceRegistry();
ConfigurationService config = serviceRegistry.getService(ConfigurationService.class);
String setting = config.getSetting(AvailableSettings.HBM2DDL_AUTO, String.class, null);
return (setting != null || ENABLED_ON.contains(setting));
}
private void addEnumsAsTableConstantsAndFkConstraint(InFlightMetadataCollector metadataCollector) {
for (PersistentClass persistentClass : metadataCollector.getEntityBindings()) {
Class<?> plainJavaClass = persistentClass.getMappedClass();
if (Enum.class.isAssignableFrom((plainJavaClass))) {
createEnumInsertsAndDbColumns(persistentClass, plainJavaClass, metadataCollector);
}
tryAddFkConstraint(persistentClass, metadataCollector);
}
}
private void tryAddFkConstraint(PersistentClass persistentClass, InFlightMetadataCollector metadataCollector) {
Consumer<Field> createEnumFkConstraintForField = field -> {
String fieldName = field.getName();
PersistentClass targetPersistentClass = metadataCollector.getEntityBinding(field.getType().getCanonicalName());
if (targetPersistentClass == null) {
LOG.error("Target (enum) class must be an #Entity: {}", field.getType().getCanonicalName());
System.exit(1);
}
Property enumReferenceAnnotatedProperty = persistentClass.getProperty(fieldName);
persistentClass.getTable().createForeignKey(null,
Arrays.asList(enumReferenceAnnotatedProperty.getColumnIterator().next()),
targetPersistentClass.getEntityName());
};
Field[] declaredFields = persistentClass.getMappedClass().getDeclaredFields();
of(declaredFields).filter(field -> field.isAnnotationPresent(EnumReference.class)).forEach(
createEnumFkConstraintForField);
}
private void createEnumInsertsAndDbColumns(PersistentClass persistentClass, Class<?> clazz,
InFlightMetadataCollector metadata) {
String tableName = persistentClass.getTable().getName();
Enum<?>[] enumJavaConstants = clazz.asSubclass(Enum.class).getEnumConstants();
ArrayList<String> insertCommandAccumulator = new ArrayList<String>(enumJavaConstants.length);
Optional<Field> enumValueAnnotatedField = of(enumJavaConstants.getClass().getComponentType().getDeclaredFields())
.filter(field -> field.isAnnotationPresent(EnumValue.class)).map(fieldWithEnumValue -> {
fieldWithEnumValue.setAccessible(true);
return fieldWithEnumValue;
}).findAny(); // just none or one is supported
if (enumValueAnnotatedField.isPresent()) {
setMinimalFieldLengthOfExitingColumn(enumValueAnnotatedField.get(), enumJavaConstants, persistentClass);
}
for (int i = 0; i < enumJavaConstants.length; i++) {
Enum<?> it = enumJavaConstants[i];
String constantEnumValue = enumValueAnnotatedField.map(v -> getInstanceValueOfEnumValueAnnotation(it, v))
.orElse(it.name());
if (!enumValueAnnotatedField.isPresent()) {
insertAdditionalColumn(persistentClass, metadata.getDatabase(), enumJavaConstants);
}
insertCommandAccumulator.add(createInsert(tableName, i, constantEnumValue));
}
InitCommand initCommand = new InitCommand(insertCommandAccumulator.toArray(new String[0]));
persistentClass.getTable().addInitCommand(initCommand);
}
private void setMinimalFieldLengthOfExitingColumn(Field field, Enum<?>[] enumJavaConstants,
PersistentClass persistentClass) {
Property property = persistentClass.getProperty(field.getName());
Column column = persistentClass.getTable().getColumn(Identifier.toIdentifier(property.getName()));
Integer maxLengthOfEnums = maxLengthOfEnums(enumJavaConstants,
e -> getInstanceValueOfEnumValueAnnotation(e, field));
column.setLength(maxLengthOfEnums);
}
private String getInstanceValueOfEnumValueAnnotation(Enum<?> myEnum, Field enumValueAnnotatedField) {
try {
return enumValueAnnotatedField.get(myEnum).toString();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
return null;
}
}
private static Integer maxLengthOfEnums(Enum<?>[] enums, Function<Enum<?>, String> enumConstantValueExtractor) {
return of(enums).map(it -> enumConstantValueExtractor.apply(it).length()).reduce(Math::max)
.orElse(DEFAULT_VARCHAR_SIZE);
};
private void insertAdditionalColumn(PersistentClass persistentClass, Database database, Enum<?>[] enumJavaConstants) {
Integer maxEnumStringLength = maxLengthOfEnums(enumJavaConstants, c -> c.name());
Column column = new Column(DEFAULT_COLUMN_NAME.render(database.getDialect()));
String typeName = database.getDialect().getTypeName(Types.VARCHAR, maxEnumStringLength, 0, 0);
column.setSqlType(typeName);
persistentClass.getTable().addColumn(column);
}
private String createInsert(String tableName, int position, String dbEnumValue) {
return ("insert into " + tableName + " values(" + position + ",\'" + dbEnumValue + "\')");
}
}
Works for MySQL 5.7 and Hibernate 5.
It is not possible to JPA query MyEnum and its consistency between #Enumerated(EnumType.ORDINAL) and getEnumConstants() order is implicitly assumed.
IMHO that does not have too much sense. The values of an enum are static and constant, while the values in a SQL table are dynamic. What if the DB does not contain exactly the values (no more, no less) for such enum?
EDIT: if you are forced to implement it, may something like this work?
public enum State{
int primaryKey;
NEW(0),OLD(1);
public State(int pk) {
primarykey = pk;
}
}
And then join by primary key....
Im having a trouble with my code. i have a view jsp where i can view all items.
When i try to search for a name. It will loop or have a redundancy. I dont know why. looks like this.
BTW i have two tables and a foreign key product[pid] to stock[pid]
public class Product {
#Id
#Column(name="p_id")
private String pid;
#Column(name="p_name")
private String p_name;
#Column(name="c_name")
private String c_name;
#Column(name="b_name")
private String b_name;
//SETTERS & GETTERS
public class Stock {
#Id
#Column(name="s_id")
private int sid;
#Column(name="p_id")
private String pid;
#Column(name="s_quantity")
private String squantity;
#Column(name="s_price")
private String sprice;
#Column(name="s_cost")
private String cost;
//SETTERS AND GETTERS
#Controller
#RequestMapping(method = RequestMethod.POST, value = "/searchItem")
public String searchItem(HttpServletRequest request, ModelMap map,
#RequestParam(value = "page", required = false) Integer page,
#RequestParam(value = "size", required = false) Integer size ) {
String searchProductName = request.getParameter("productName");
String cat = request.getParameter("category");
String bran = request.getParameter("brand");
Product searchProduct = new Product();
searchProduct.setP_name(searchProductName);
searchProduct.setC_name(cat);
searchProduct.setB_name(bran);
int pageSize = (size != null && size != 0) ? size : 25;
int firstResultIndex = (page != null && page > 0) ? (page - 1) * pageSize : 0;
List<Product> productList = catService.getUsers(searchProduct, firstResultIndex, pageSize);
map.addAttribute("productList", productList);
List<Category> cList = catService.getCat();
map.addAttribute("cList", cList);
List<Brand> bList = catService.getBrand();
map.addAttribute("bList", bList);
return "new/list";
}
#DaoImpl
#SuppressWarnings("unchecked")
#Override
public List<Product> getUsers(Product searchProduct, int startPage, int maxResults) {
EntityManager entityManager = transactionManager.getEntityManagerFactory().createEntityManager();
Session session = entityManager.unwrap(Session.class);
SQLQuery query = session.createSQLQuery("FROM product,stock");
boolean paramExists = false;
if (!StringUtility.isStringNullOrEmpty(searchProduct.getC_name())&&!StringUtility.isStringNullOrEmpty(searchProduct.getB_name())) {
//sqlQuerySB.append(" product.c_name LIKE :category AND product.b_name LIKE :brand");
query = session.createSQLQuery("FROM product,stock WHERE product.c_name LIKE :category AND product.b_name LIKE :brand");
paramExists = true;
}
if (!StringUtility.isStringNullOrEmpty(searchProduct.getP_name())) {
query = session.createSQLQuery("SELECT product.p_name,product.c_name,product.b_name,stock.s_quantity,stock.s_price,stock.s_cost FROM product,stock WHERE product.p_name LIKE :productName");
query.setParameter("productName", "%" + searchProduct.getP_name() + "%");
paramExists = true;
}
if (!StringUtility.isStringNullOrEmpty(searchProduct.getC_name())) {
query = session.createSQLQuery("SELECT product.p_name,product.c_name,product.b_name,stock.s_quantity,stock.s_price,stock.s_cost FROM product,stock WHERE product.c_name LIKE :category ");
query.setParameter("category", "" + searchProduct.getC_name() + "");
paramExists = true;
}
query.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List<Product> productList = query.list();
if (entityManager.isOpen())
entityManager.close();
return productList;
}
maybe there is a big problem in my DAOimpl on how i query two tables..
need some help/advice. ty.
You are not specifying the join criteria between product and stock:
SELECT
product.p_name,
product.c_name,
product.b_name,
stock.s_quantity,
stock.s_price,
stock.s_cost
FROM product, stock
WHERE product.p_name LIKE :productName
In this case it will return one row for each for each combination of product (with the name) and stock (full table since it does not have any criteria).
Try to specify the join criteria:
SELECT
product.p_name,
product.c_name,
product.b_name,
stock.s_quantity,
stock.s_price,
stock.s_cost
FROM product, stock
WHERE
product.pid = stock.pid
product.p_name LIKE :productName
1 2: select (table.*)/(all column) is OK
String sql = "select t_student.* from t_student";
//String sql = "select t_student.id,t_student.name,... from t_student"; //select all column
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Student.class);//or query.addEntity("alias", Student.class);
//query.list();[Student#..., Student#..., Student#...]
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP); //or other transformer
query.list(); //[{Student(or alias)=Student#...},{Student=Student#...}]
3: select some column(not all of), is Error
String sql = "select t_student.id,t_student.name.t_student.sex from t_student";
SQLQuery query = session.createSQLQuery(sql);
query.addEntity(Student.class);
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
query.list(); //Exception:invalid column/no column
I want "3" to work ok, and let the result can be mapped to Student.class.
Like: Student[id=?, name=?, sex=?, (other field are null/default)]
I've no idea for this error, help me please!
You can go further and add
.setResultTransformer(Transformers.aliasToBean(YOUR_DTO.class));
and automatically map it to your custom dto object, see also Returning non-managed entities.
For example:
public List<MessageExtDto> getMessagesForProfile2(Long userProfileId) {
Query query = getSession().createSQLQuery(" "
+ " select a.*, b.* "
+ " from messageVO AS a "
+ " INNER JOIN ( SELECT max(id) AS id, count(*) AS count FROM messageVO GROUP BY messageConversation_id) as b ON a.id = b.id "
+ " where a.id > 0 "
+ " ")
.addScalar("id", new LongType())
.addScalar("message", new StringType())
......... your mappings
.setResultTransformer(Transformers.aliasToBean(MessageExtDto.class));
List<MessageExtDto> list = query.list();
return list;
}
I want "3" to work ok, and let the result can be mapped to Student.class
That's possible using
Query createNativeQuery(String sqlString, String resultSetMapping)
In the second argument you could tell the name of the result mapping. For example:
1) Let's consider a Student entity, the magic is going to be in the SqlResultSetMapping annotation:
import javax.persistence.Entity;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.Table;
#Entity
#Table(name = "student")
#SqlResultSetMapping(name = "STUDENT_MAPPING", classes = {#ConstructorResult(
targetClass = Student.class, columns = {
#ColumnResult(name = "name"),
#ColumnResult(name = "address")
})})
public class Student implements Serializable {
private String name;
private String address;
/* Constructor for the result mapping; the key is the order of the args*/
public Student(String aName, String anAddress) {
this.name = aName;
this.address = anAddress;
}
// the rest of the entity
}
2) Now you can execute a query which results will be mapped by STUDENT_MAPPING logic:
String query = "SELECT s FROM student s";
String mapping = "STUDENT_MAPPING";
Query query = myEntityManager.createNativeQuery(query, mapping);
#SuppressWarnings("unchecked")
List<Student> students = query.getResultList();
for (Student s : students) {
s.getName(); // ...
}
Note: I think it's not possible to avoid the unchecked warning.
There is only two ways.
You can use 1st or 2nd snippet. According to Hibernate documentation you must prefer 2nd.
You can get just a list of object arrays, like this:
String sql = "select name, sex from t_student";
SQLQuery query = session.createSQLQuery(sql);
query.addScalar("name", StringType.INSTANCE);
query.addScalar("sex", StringType.INSTANCE);
query.list();
I had same problem on HQL Query. I solved the problem by changing the transformer.
The problem caused the code written to transform as Map. But it is not suitable for Alias Bean. You can see the error code at below. The code written to cast result as map and put new field to the map.
Class : org.hibernate.property.access.internal.PropertyAccessMapImpl.SetterImpl
m
Method: set
#Override
#SuppressWarnings("unchecked")
public void set(Object target, Object value, SessionFactoryImplementor factory) {
( (Map) target ).put( propertyName, value );
}
I solved the problem to duplicate the transformer and change the code.
You can see the code in the project.
Link : https://github.com/robeio/robe/blob/DW1.0-migration/robe-hibernate/src/main/java/io/robe/hibernate/criteria/impl/hql/AliasToBeanResultTransformer.java
Class:
import java.lang.reflect.Field;
import java.util.Map;
import io.robe.hibernate.criteria.api.query.SearchQuery;
import org.hibernate.HibernateException;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer {
private static final Logger LOGGER = LoggerFactory.getLogger(AliasToBeanResultTransformer.class);
private final Class resultClass;
// Holds fields of Transform Class as Map. Key is name of field.
private Map<String, Field> fieldMap;
public AliasToBeanResultTransformer(Class resultClass) {
if ( resultClass == null ) {
throw new IllegalArgumentException( "resultClass cannot be null" );
}
fieldMap = SearchQuery.CacheFields.getCachedFields(resultClass);
this.resultClass = resultClass;
}
#Override
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
#Override
public Object transformTuple(Object[] tuple, String[] aliases) {
Object result;
try {
result = resultClass.newInstance();
for ( int i = 0; i < aliases.length; i++ ) {
String name = aliases[i];
Field field = fieldMap.get(name);
if(field == null) {
LOGGER.error(name + " field not found in " + resultClass.getName() + " class ! ");
continue;
}
field.set(result, tuple[i]);
}
}
catch ( InstantiationException e ) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
} catch ( IllegalAccessException e ) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
}
return result;
}
}
After created new Transformer You can use like below.
query.setResultTransformer(new AliasToBeanResultTransformer(YOUR_DTO.class));
You can mapped it automatically:
Your Model Student.java
public class Student {
private String name;
private String address;
}
Repository
String sql = "Select * from student";
Query query = em.createNativeQuery(sql, Student.class);
List ls = query.getResultList();
so it will automatically mapped the result with the Student class