Postgres / hibernate operator does not exist: text = bytea - java

I am new to the hibernate world and I am getting the following error message when trying to execute a query with hibernate and postgres.
org.postgresql.util.PSQLException: ERROR: operator does not exist: text = bytea
Hint: No operator matches the given name and argument type(s). You might
need to add explicit type casts.
Here is my hibernate mapping (car.hbm.xml):
<hibernate-mapping>
<class name="Car" table="car"
schema="someSchema">
<id name="id" type="int" column="car_id">
<generator class="sequence">
<param name="sequence">car_seq</param>
</generator>
</id>
<property name="carMake">
<column name="car_make" sql-type="string"/>
</property>
<property name="carModel">
<column name="car_model" sql-type="string"/>
</property>
<property name="carVin" >
<column name="car_vin" sql-type="int" />
</property>
<property name="datePurchased">
<column name="date_purchased" sql-type="date"/>
</property>
<property name="retiredModel">
<column name="retired_model" sql-type="boolean"/>
</property>
</class>
On Postgres, here is what my table looks like:
CREATE TABLE car (
car_vin INTEGER NOT NULL DEFAULT nextval('car_seq'::regclass) PRIMARY KEY,
car_make TEXT NOT NULL,
car_model TEXT DEFAULT NULL,
date_purchased DATE DEFAULT now() NOT NULL,
retired_model BOOLEAN DEFAULT FALSE NOT NULL
);
Here is my model class (Car.java):
public class Car {
private int id;
private String carMake;
private String carModel;
private int carVin;
private Date datePurchased;
private boolean retiredModel;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCarModel() {
return carModel;
}
public void setcarModel(String carModel) {
this.carModel = carModel;
}
public String getcarMake() {
return carMake;
}
public void setcarMake(String carMake) {
this.carMake = carMake;
}
public Date getDatePurchased() {
return datePurchased;
}
public void setDatePurchased(Date datePurchased) {
this.datePurchased = datePurchased;
}
public boolean isRetired() {
return retiredModel;
}
public void setRetired(boolean retiredModel) {
this.retiredModel = retiredModel;
}
In my DAO layer, I am using the following line to query:
Query query = getSession().createQuery("from Car as c where " +
"c.carModel = ? AND c.carMake = ?").setParameter(0, carModel).setParameter(1, carMake);
carMake and carModel are both String datatypes passed on as method parameters in the DAO method.
Note that the strings in my hbm are mapped to TEXT in postgres, so I am guessing if that is the problem or not. If it is, how do I solve it ?

It is weird but the query does not handle null very well. When I changed the query to:
Query query = getSession().createQuery("from Car as c where " +
"c.carModel = ? AND c.carMake is null").setParameter(0, carModel);
it works fine since the DAO needs to query the make as NULL. So if it is not null, I need to have two sets of query, one that is hardcoded to select null as above, other to setParam(1, carMake).
Weird but I think this works.

Usually this error is from Hibernate serializing a not-otherwise-mapped class (resulting in a bytea) and comparing that to a String (probably given by you in a query).
Map the Date! Use #Temporal(Date) on the Date attribute. I don't know how to express that in hbm.xml notation.

Use Query.setParameterList instead of setParameter solved my problem for an integer array (integer = bytea)

I had the same problem and in my case it was caused by the fact that I was trying to use a Java enum class as a named parameter within a native query.
The solution was to go all the way with Hibernate, so non-native query and using Java class and member names instead of table name and column names.

Related

Hibernate unable to delete child record

I'm unable to delete a child record while I'm updating (not deleting) the parent record. Also, I've read other posts, but it seems most of the others are using annotations rather than xml, so it can be difficult to see how they relate to my issue.
I have two tables: The EventInfo table that holds information about events and then the EventLicenseType table that only has two columns and both of those columns make up the primary key; one of the columns in the EventLicenseType table is a foreign key to the EventInfo table.
The problem is I can't seem to delete an EventLicenseType record. I've tried a bunch of different things and nothing is working for me. It seems like hibernate wants to put null as the eventinfoId column, which of course doesn't work. I have tried clearing out the Set and then doing the merge, and also specifically calling session.delete(eventlicenseTypeRec) and then doing the merge. Neither is working for me.
EventInfo.hbm.xml file:
<hibernate-mapping default-lazy="true">
<class name="Eventinfo" table="PA_EVENTINFO">
<id name="eventInfoId" type="int"
column="PA_EVENTINFOID">
<generator class="native" />
</id>
<property name="eventTypeId" type="java.lang.String"
column="PA_EVENTTYPEID" length="255" />
...Other columns not shown here for brevity...
<set name="eventLicenceTypeIds" lazy="false" cascade="all-delete-orphan">
<key column="PA_EVENTINFOID"/>
<one-to-many class="EventLicenseType" />
</set>
</class>
EventLicenseType.hbm.xml file:
<hibernate-mapping default-lazy="true">
<class name="EventLicenseType" table="PA_EVENTLICENSETYPE">
<composite-id>
<key-property name="licenseTypeId" type="java.lang.Integer" column="PA_LICENSETYPE"/>
<key-property name="eventInfoId" type="java.lang.Integer" column="PA_EVENTINFOID"/>
</composite-id>
</class>
Here is the EventInfo class. Again, there are more fields in the actual file, this is just the important pieces:
public class Eventinfo implements Serializable {
/** identifier field */
private int eventInfoId;
/** nullable persistent field */
#Field(name="eventInfo_eventTypeId")
private String eventTypeId;
#IndexedEmbedded
private Set<EventLicenseType> eventLicenceTypeIds;
/** default constructor */
public Eventinfo() {}
public int getEventInfoId() {
return this.eventInfoId;
}
public void setEventInfoId(int eventInfoId) {
this.eventInfoId = eventInfoId;
}
public String getEventTypeId() {
return this.eventTypeId;
}
public void setEventTypeId(String eventTypeId) {
this.eventTypeId = eventTypeId;
}
public Set<EventLicenseType> getEventLicenceTypeIds() {
return eventLicenceTypeIds;
}
public void setEventLicenceTypeIds(Set<EventLicenseType> eventLicenceTypeIds) {
this.eventLicenceTypeIds = eventLicenceTypeIds;
}
Here is the EventLicenseType class
public class EventLicenseType implements Serializable{
#Field
private int licenseTypeId;
private int eventInfoId;
public int getLicenseTypeId() {
return licenseTypeId;
}
public void setLicenseTypeId(int licenseTypeId) {
this.licenseTypeId = licenseTypeId;
}
public int getEventInfoId() {
return eventInfoId;
}
public void setEventInfoId(int eventInfoId) {
this.eventInfoId = eventInfoId;
}
}
Here is the method I'm executing in my DAO. For now there is only one record associated to the eventInfo record, so I'm just trying to see if I can delete that one. (Also note that eventinfo is defined in the method that surrounds this one).
public Eventinfo execute(Session session) throws Exception {
//Get the existing eventInfo record
Eventinfo existing = (Eventinfo)session.get(Eventinfo.class, eventinfo.getEventInfoId());
Iterator iter = existing.getEventLicenceTypeIds().iterator();
if (iter.hasNext()) {
EventLicenseType license = (EventLicenseType) iter.next();
iter.remove();
session.delete(license);
}
session.flush();
return (Eventinfo) session.merge(eventinfo);
}
On the above session.flush() line, I get an error: java.sql.BatchUpdateException: Cannot insert the value NULL into column 'PA_EVENTINFOID', table 'PA_EVENTLICENSETYPE'; column does not allow nulls. UPDATE fails. It shows that hibernate is trying to do:
update PA_EVENTLICENSETYPE set PA_EVENTINFOID=null where PA_EVENTINFOID=?
Why can't it just delete the record? Why is it trying to do an update??
I also tried changing the code to the below and get the same error.
public Eventinfo execute(Session session) throws Exception {
//Clear out the list
eventinfo.getEventLicenceTypeIds().clear();
return (Eventinfo) session.merge(eventinfo);
}
Can anyone help me with what I'm missing, or point me in the right direction?
You need to see whether the mapping between two tables are unilateral or bilateral. Basically you need to think or the rows of two tables as objects and cut all the ties between the objects of EventInfo and EventLicenceType tables. So if the relation is only from EventInfo -> EventLicenseType, you need to set the value of the EventLicenseType set to null in EventInfo object. Also if there is a mapping from EventLicenseType, you need to set the value of joining column to null. Then merge() the EventInfo object.
Noneed to explicitly delete or remove the EventLicenseType object. If here are no references to EventLicenseType object, the JVM will collect it as garbage.
Hope that solves your problem.

MyBatis, Select Provider and SQLBuilder

this is more than a simple question and my English is not as good as I want... I'll try my best.
I use java 8, with Mybatis 3.4.6 over Postgres 9.6 and I need to do a custom dynamic query.
In my mapper.java class I've created a method to use with myBatis SQL Builder class
#SelectProvider(type = PreIngestManager.class, method = "selectPreIngestsSQLBuilder")
#Results({ #Result(property = "id", column = "id"), #Result(property = "inputPath", column = "input_path"),
#Result(property = "idCategoriaDocumentale", column = "id_categoria_documentale"), #Result(property = "idCliente", column = "id_cliente"),
#Result(property = "outputSipPath", column = "output_sip_path"), #Result(property = "esito", column = "esito"),
#Result(property = "stato", column = "stato"), #Result(property = "pathRdp", column = "path_rdp"),
#Result(property = "dataInizio", column = "data_inizio"), #Result(property = "dataFine", column = "data_fine") })
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto,
#Param("dataInizioInferiore") Date dataInizioInferiore, #Param("dataInizioSuperiore") Date dataInizioSuperiore,
#Param("statiPreIngest") String statiPreIngest);
I have specified the #SelectProvider annotation, class and method to point at, which, in the example is PreIngestManager.class and selectPreIngestsSQLBuilder method.
This is the method
public String selectPreIngestsSQLBuilder(Map<String, Object> params) {
return new SQL() {
{
SELECT("*");
FROM("pre_ingest");
WHERE("id_categoria_documentale = #{idCatDoc}");
if (params.get("nomePacchetto") != null)
WHERE("input_path like '%' || #{nomePacchetto}");
if (params.get("dataInizioInferiore") != null) {
if (params.get("dataInizioSuperiore") != null) {
WHERE("data_inizio between #{dataInizioInferiore} and #{dataInizioSuperiore}");
} else {
WHERE("data_inizio >= #{dataInizioInferiore}");
}
} else {
if (params.get("dataInizioSuperiore") != null) {
WHERE("data_inizio <= #{dataInizioSuperiore}");
}
}
if (params.get("statiPreIngest") != null)
WHERE("stato in (#{statiPreIngest})");
ORDER_BY("id ASC");
}
}.toString();
}
and these are my questions:
have I to specify #Results annotation and every #Result , or can I use a java model class ? I have tried with #ResultMap(value = { "mycompany.model.PreIngest" }) but it did not work.
Most of all, as stated on documentation, with SQL builder you can access method parameters having them as final objects
// With conditionals (note the final parameters, required for the anonymous inner class to access them)
public String selectPersonLike(final String id, final String firstName,
final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID like #{id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
But if I put those final in my method I can't access them. Do I need to delete the #Param from the method declaration? Do SQLBuilder need to be called without #SelectProvider ? Am I mixing solutions ?
As far as I have researched, for now I see 3 methods to do a dynamic query, or a custom where condition.
To use MyBatisGenerator library and combine where condition as search criteria to use with SelectByExample method. (I use this when the query is simple)
To Write an SQL query directly, modifying XML mapper files using if, choose, statements and others as descripted here
To use SQL Builder class with #SelectProvider annotation.
Do you know when prefer the 2° method over the 3° one ? Why in the 3° method documentation I can't find how to use it ? There is written how to create custom queries but not how to launch them.
Thank a lot for your time and your suggestions.
I don't know whether you already found the answer, I just want to share my experience. Btw please forgive my english if it wasn't good.
Note: I use MyBatis 3.4.6 and Spring Framework.
have I to specify #Results annotation and every #Result , or can I use a java model class ?
Actually you can do either one.
if you want to use #Results and #ResultMap, you just need to specify #Results annotation just once in one mapper file. The trick is you need to specify id for the Results to be used in other functions.
Using truncated version of your classes, eg:
#Results(id="myResult", value= {
#Result(property = "id", column = "id"),
#Result(property = "inputPath", column = "input_path"),
#Result(property = "idCategoriaDocumentale", ... })
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto, ...);
Then in another function you can use #ResultMap with the value refer to id from #Results mentioned before.
#ResultMap("myResult")
List<PreIngest> selectPreIngestsBySomethingElse(....);
..., or can I use a java model class ?
You can use java model class as result without using #Results and #ResultMap, but you have to make sure your java model class has the same properties/fields as the result of your query. Database tables usually have fields with snake_case. Since java is using camelCase, you have to add settings to your mybatis-config.xml file.
This is what I usually add to the mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- changes from the defaults -->
<setting name="lazyLoadingEnabled" value="false" />
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
</configuration>
The important one is mapUnderscoreToCamelCase, set this to true than you can use your java model class without the hassle of #Results and #ResultMap. You can find all the explanation of the settings in MyBatis 3 Configuration.
This is the example using your classes,
The class:
public class PreIngest {
private Long idCategoriaDocumentale;
private Long idCliente;
........ other fields
........ setter, getter, etc
}
The mapper file:
List<PreIngest> selectPreIngestsByFilters(#Param("idCatDoc") Long idCatDoc, #Param("nomePacchetto") String nomePacchetto, ...);
Now onwards to SqlBuilder.
But if I put those final in my method I can't access them. Do I need to delete the #Param from the method declaration? Do SQLBuilder need to be called without #SelectProvider ?
I can't answer about those final in your method since I never made SqlBuilder class with final parameters.
For SqlBuilder you must use #SelectProvider, #InsertProvider, #UpdateProvider or #DeleteProvider and it depends on the query you use.
In my experience with SQLBuilder, #Param is necessary if you need more than one parameters and use Map params to access it from the SqlBuilder class. If you don't want to use #Param in the mapper file, then you need to make sure there is only one parameter in the said mapper function. You can use java model class as the parameter though if you just specify one parameter.
If using your class for example, you can have one class
public class PersonFilter {
private Long id;
private String firstName;
private String lastName;
...... setter, getter, etc
}
the mapper function
#SelectProvider(type=PersonSqlBuilder.class, method="selectPersonLike")
List<Person> selectPersonLike(PersonFilter filter);
the SqlBuilder class
public class PersonSqlBuilder {
public String selectPersonLike(PersonFilter filter) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (filter.getId() != null) {
WHERE("P.ID like #{id}");
}
if (filter.getFirstName() != null) {
WHERE("P.FIRST_NAME like #{firstName}");
}
if (filter.getLastName() != null) {
WHERE("P.LAST_NAME like #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
}
That's it. Hopefully my experience can help.
I don't know how to do this with the sql builder but I do have an idea how to do this with an xml mapper file:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="path.to.class.PreIngestMapper">
<resultMap id="preIngestManager" type="path.to.class.PreIngestManager">
<id property="id" column="id" />
<result property="id" column="id" />
<result property="inputPath" column="input_path" />
<result property="idCategoriaDocumentale" column="id_categoria_documentale" />
...
</resultMap>
<select id="selectPreIngests" parameterType="Map" resultMap="preIngestManager">
SELECT *
FROM pre_ingest
WHERE id_categoria_documentale = #{idCatDoc}
<if test = "nomePacchetto != null">
and input_path like '%' || #{nomePacchetto}
</if>
...
;
</select>
</mapper>

Spring: Generic RowMapper for dynamic queries

I am using SpringBatch to read from Oracle and write into ElasticSearch.
My code works fine for static queries.
Example: select emp_id, emp_name from employee_table I have a RowMapper class that maps the values from resultSet with the Employee POJO.
My requirement is
The query will be input by the user. So the query might be as follows
select emp_id, emp_name from employee_table
select cust_id, cust_name, cust_age from customer_table
select door_no, street_name, loc_name, city from address_table
Similar queries
My questions are
Is there a way to dynamically create a POJO according to the query given by the user?
Will the RowMapper concept work if the query keeps changing as in my case?
Is there something like a generic rowmapper?
Sample code would be much appreciated.
If you have objects you need to map to...
Consider aliasing your SQL to match your object field names using a custom implementation of RowMapper which actually extends BeanWrapperFieldSetMapper
So if your POJO looks like this:
public class Employee {
private String employeeId;
private String employeeName;
...
// getters and setters
}
Then your SQL can look like this:
SELECT emp_id employeeId, emp_name employeeName from employee_table
Then your wrapped RowMapper would look something like this:
import org.springframework.jdbc.core.RowMapper
import org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper
public class BeanWrapperRowMapper<T> extends BeanWrapperFieldSetMapper<T> implements RowMapper<T> {
#Override
public T mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final FieldSet fs = getFieldSet(rs);
try {
return super.mapFieldSet(fs);
} catch (final BindException e) {
throw new IllegalArgumentException("Could not bind bean to FieldSet", e);
}
}
private FieldSet getFieldSet(final ResultSet rs) throws SQLException {
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
final List<String> tokens = new ArrayList<>();
final List<String> names = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
tokens.add(rs.getString(i));
names.add(metaData.getColumnName(i));
}
return new DefaultFieldSet(tokens.toArray(new String[0]), names.toArray(new String[0]));
}
}
Alternatively...
If you don't have any POJOs to map to, use the out-of-box ColumnMapRowMapper to get get back a map (Map<String,Object>) of column names (let's call them COL_A, COL_B, COL_C) to values. Then if your writer is something like a JdbcBatchItemWriter you can set your named parameters as:
INSERT TO ${schema}.TARGET_TABLE (COL_1, COL_2, COL_3) values (:COL_A, :COL_B, :COL_C)
and then your ItemSqlParameterSourceProvider implementation could look like so:
public class MapItemSqlParameterSourceProvider implements
ItemSqlParameterSourceProvider<Map<String, Object>> {
public SqlParameterSource createSqlParameterSource(Map<String, Object> item) {
return new MapSqlParameterSource(item);
}
}
To answer your questions:
Is there a way to dynamically create a POJO based on the user's query - Even if there was, I'm not sure how much help it would be. For your use case, I'd suggest just using a Map.
Will the RowMapper concept work if the query keeps changing - If you use a Map, you can use the column names as the keys and the column values as the values. You should be able to create a RowMapper implementation that can do this.
Is there something like a generic RowMapper - There is but it's intended for POJO's so you'd need to create your own for this.
You can do it simply like below,
SettingsDto settings = SettingsDao.getById(1, new BeanPropertyRowMapper<>(SettingsDto.class));
In a generic way, you can pass your DTO class, but please note you have to use same name as SQL columns or have to use ALIAS in SQL query according to the DTO.
#Data
public class SettingsDto {
private int id;
private int retryCount;
private int batchSize;
private int retryPeriod;
private int statusInitialDelay;
}
My dao method is below
SettingsDto getById(int id, final RowMapper<OMoneySettingsDto> mapper);
its implementation is below,
#Override
public SettingsDto getById(final int id, final RowMapper<OMoneySettingsDto> mapper) {
return new JdbcTemplate(YourDataSource).queryForObject(QUERY_SETTINGS_BY_ID,new Object[]{id}, mapper);
}
SQL is here, as below you have to use same name in the DTO
private static final String OMONEY_SETTINGS_BY_ID = "SELECT AS id,retry_count AS retryCount FROM setttings WHERE id = ?";
I found a solution to my problem by using Spring's ColumnMapRowMapper. Please find a snippet from the xml configuration file. I didn't generate any POJO class. I managed with a Map and inserted the same into ES. The map's key name should match with the field names present in index.
<step id="slave" xmlns="http://www.springframework.org/schema/batch">
<tasklet>
<chunk reader="pagingItemReader" writer="elasticSearcItemWriter"
processor="itemProcessor" commit-interval="10" />
</tasklet>
</step>
<bean id="pagingItemReader"
class="org.springframework.batch.item.database.JdbcPagingItemReader"
scope="step">
<property name="dataSource" ref="dataSource" />
<property name="queryProvider">
<bean
class="org.springframework.batch.item.database.support.SqlPagingQueryProviderFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="selectClause" value="*******" />
<property name="fromClause" value="*******" />
<property name="whereClause" value="*******" />
<property name="sortKey" value="*******" />
</bean>
</property>
<!-- Inject via the ExecutionContext in rangePartitioner -->
<property name="parameterValues">
<map>
<entry key="fromId" value="#{stepExecutionContext[fromId]}" />
<entry key="toId" value="#{stepExecutionContext[toId]}" />
</map>
</property>
<property name="pageSize" value="10" />
<property name="rowMapper">
<bean class="org.springframework.jdbc.core.ColumnMapRowMapper" />
</property>
</bean>
And inside my elasticSearcItemWriter class....
public class ElasticSearchItemWriter<T> extends AbstractItemStreamItemWriter<T>
implements ResourceAwareItemWriterItemStream<T>, InitializingBean {
....
....
....
#Override
public void write(List<? extends T> items) throws Exception {
client = jestClient.getJestClient();
if (items.size() > 0) {
for (Object item : items) {
#SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) item;
// Asynch index
Index index = new Index.Builder(map).index(Start.prop.getProperty(Constants.ES_INDEX_NAME))
.type(Start.prop.getProperty(Constants.ES_INDEX_TYPE)).build();
client.executeAsync(index, new JestResultHandler<JestResult>() {
public void failed(Exception ex) {
}
public void completed(JestResult result) {
}
});
}
}
}
.....
....
}

Hibernate trouble getting composite key to work

I have a class called WebAsset:
public class WebAsset {
private Long id;
private String url;
private int status;
//more fields that are not relevent
}
I need to be able to show relationships between WebAsset, so I created a table for the relationship and a composite key class.
public class WebAssetReferencePK {
private Long sourceAssetId;
private Long targetAssetId;
}
public class WebAssetReference {
private WebAssetReferencePK wpk;
private Long updateTime;
}
We are forced to use an older version of Hibernate so we need to use xml files instead of annotaions. Here is the mapping for the reference class:
<class name="ca.gc.cra.www.crawler.valueobject.WebAssetReference" table="webassetreference">
<composite-id name="webAssetReferencePK" class="ca.gc.cra.www.crawler.valueobject.WebAssetReferencePK">
<key-property name="sourceAsset" type="java.lang.Long" column="sourceAssetId" />
<key-property name="targetAsset" type="java.lang.Long" column="targetAssetId" />
</composite-id>
<property name="updateTime" type="java.lang.Long" column="updatetime" not-null="true" />
</class>
In the composite key I get what I expect in the database with 2 ids related to each other. But when I try to query with HQL or Criteria it doesn't work since there is no direct relation between the PK class and WebAsset and I need to be able to do a join between WebAsset and WebAssetReference. If I try to change the composite key types from java.lang.Long to WebAsset then hibernate stores the whole object in the WebAssetReference table instead of just the ids.
An example of what I am trying to do is if I have a sourceAssetId I want to return all the targetAssetIds with the same source, but I don't want the ids themselves I want the WebAsset that is the primary key for each targetAssetId.
I have been searching around for the answer but every example I can find are just simple examples that don't relate.
Update 1: With continued searching I finally found the answer. Instead of key-property I need to use key-many-to-one. I haven't tried a join yet but everything else looks right so this should be the answer.
Update 2: Can't get the query to work with HQL. Here is th SQL of what I am trying to do:
select * from webasset as wa join webassetreference as war on war.targetassetid=wa.webasset_id where war.sourceassetid=?
Here is the HQL that is not working:
FROM WebAsset JOIN WebAssetReference WebAssetReference.WebAssetReferencePK.targetAsset=WebAsset WHERE WebAssetReference.WebAssetReferencePK.sourceAsset = :sourceAsset
I get the following error with HQL: ERROR - line 1:89: unexpected token: .
I'll keep trying but I can't seem to figure out the HQL.
I discovered how to do this. In the case I have above it will not work since I have 2 columns joining to the same table. However if I use the same WebAsset class above and instead use this class:
public class TreeNode implements Comparable<TreeNode>{
private String nodeUrl;
private Long id;
private Boolean folder;
private transient WebAsset nodeAsset = null;
}
With this .hbm.xml file:
<class name="ca.gc.cra.www.crawler.valueobject.TreeNode" table="TreeNode">
<id name="id" type="java.lang.Long" column="treenode_id" >
<generator class="identity"/>
</id>
<many-to-one name="nodeAsset" class="ca.gc.cra.www.crawler.valueobject.WebAsset" column="nodeAsset_id" lazy="false" not-null="false" cascade="none" unique="true" />
<property name="folder" type="java.lang.Boolean" column="folder" not-null="true" />
<property name="nodeUrl" length="512" type="java.lang.String" column="nodeUrl" not-null="true" />
<set name="children" table="TreeNode" inverse="false" lazy="true" >
<key column="parentnode_id"/>
<one-to-many class="ca.gc.cra.www.crawler.valueobject.TreeNode" />
</set>
</class>
You can then use this code to retrieve the join:
Session session = HibernateUtil.getSession();
try {
String hql = "FROM TreeNode tn JOIN tn.nodeAsset WHERE tn.id=5";
Query query = session.createQuery(hql);
List result = query.list();
System.out.println("done");
} catch (HibernateException e) {
e.printStackTrace();
throw new Exception("Query failed", e);
} finally {
session.flush();
session.close();
}
Hibernate can then perform the join correctly. The result will be a List containing an Object array for each entry. The Object contains the 2 classes that are part of the join. You have to cast the Object with (Object[]) to access the elements and then cast each on to the appropriate class.
I would recommend against this approach because Hibernate will attempt to load all connected classes as well. With the example above I was getting 1 row from TreeNode yet it generated 19 select statements. I even attempted to set the connected classes to lazy load and it still generated all the selects.

Hibernate : Table doesn't get updated

I'm having trouble with my hibernate transaction. After execution , table has no values in it and is not updated at all. All my other updates work properly.
Here's the mapping
<class name="LastDownloadedMessage" table="t_imap_lastmsguid">
<id name="id" column="id" ><generator class="increment"/></id>
<property name="lastDownloadedMessageUid"><column name="last_msg_uid" /></property>
<property name="lastUidNext"><column name="next_msg_uid" /></property>
<property name="folder"><column name="folder_name" /></property>
<property name="cred"><column name="credential" /></property>
</class>
This is the POJO object :
public class LastDownloadedMessage {
Integer id;
private String lastDownloadedMessageUid;
private String lastUidNext;
private String folder;
private String cred;
//GETTERS AND SETTERS HERE
public LastDownloadedMessage() {
super();
}
public LastDownloadedMessage(String lastDownloadedMessageUid,
String lastUidNext) {
super();
this.lastDownloadedMessageUid = lastDownloadedMessageUid;
this.lastUidNext = lastUidNext;
}
}
and this is the function which is doing the update.
Session ssn=HibernateSessionFactory.openSession();
Transaction txn = ssn.beginTransaction();
Query query = ssn.createQuery("update LastDownloadedMessage e set e.lastDownloadedMessageUid = :luid , e.lastUidNext = :nextUid where e.folder =:folder and e.cred = :cred");
query.setParameter("luid",last_downloaded_msg_uid);
query.setParameter("nextUid", uid_next);
query.setParameter("folder", folder);
query.setParameter("cred", credential);
int result = query.executeUpate();
txn.commit();
ssn.flush();
ssn.close();
The function appears to execute properly with no errors . What could be the issue ?
Based on the comments, it seems to me you are confusing UPDATE with INSERT.
If result is zero, it means your WHERE clause didn't match anything, so there's nothing to update.
Also, why are you trying to issue an UPDATE/INSERT on a known (mapped) entity? All you have to do is set your values on your object (LastDownloadedMessage) and then execute a persist on your EntityManager. Browse around the web for EntityManager.persist.

Categories

Resources