I have a DAO with these methods:
#SqlUpdate("INSERT INTO my_test (ba) VALUES (:ba)")
void insertBytea(#Bind("ba") byte[] ba);
#SqlQuery("SELECT ba from my_test fetch first 1 row only")
byte[] selectBytea();
When I execute the insert method:
byte[] bytea = new byte[1];
bytea[0] = 1;
myDao.insertBytea(bytea);
the value ends up in the database.
So far so good.
But when I retrieve it:
byte[] bytes = myDao.selectBytea();
.. this happens:
...
Caused by: org.postgresql.util.PSQLException: Bad value for type byte : \x01
at org.postgresql.jdbc.PgResultSet.getByte(PgResultSet.java:2135)
at org.jdbi.v3.core.mapper.PrimitiveMapperFactory.lambda$primitiveMapper$0(PrimitiveMapperFactory.java:64)
at org.jdbi.v3.core.mapper.SingleColumnMapper.lambda$new$0(SingleColumnMapper.java:41)
at org.jdbi.v3.core.mapper.SingleColumnMapper.map(SingleColumnMapper.java:55)
at org.jdbi.v3.core.result.ResultSetResultIterator.next(ResultSetResultIterator.java:83)
I'm not sure what is going on. But when I debug the code, it seems as if the postgres library is has transformed the value from byte array, to string, back to byte array?
...because the values [92, 120, 48, 49] corresponds to the string "\x01" which seems to be one of the ways postgres expresses bytea values.
I am using jdbi3 libraries to access the db.
I am depending on the artifact postgresql version 42.2.18.
JDBI internal mapper strategy detects byte[] return type as container type and expects the query to return array of byte values. But in your case it is rather a single value returned containing array of bytes.
Solution is simple, just add org.jdbi.v3.sqlobject.SingleValue annotation to your method.
From #SingleValue javadoc
Indicate to SqlObject that a type that looks like a container should be treated as a single element.
Your particular example will look like the following:
#SingleValue
#SqlQuery("SELECT ba from my_test fetch first 1 row only")
byte[] selectBytea();
Related
I want to select data from a mysql table, and then generate a sql script file for importing to another db.
The value type of blob in mysql is byte array in java. I have try to use new String(byte[], Charset) to transform it to String, using all charsets that java support. It can import successfully, but the imported value is incorrect. All the generated strings are not equal to what SQLyog generates.
how can i do?
I am trying to insert a new record into a simple database table with MyBatis but I get a strange exception. Mybe it is related to that I am not using POJO.
MyBatis version: 3.4.5
My table:
CREATE TABLE IF NOT EXISTS image
(
id BIGINT PRIMARY KEY,
content BYTEA
) WITHOUT OIDS;
MyBatis mapper:
#Insert("INSERT INTO image (id, content) VALUES (#{id}, #{content})")
#SelectKey(statement = "SELECT NEXTVAL('image_seq')", keyProperty = "id", before = true, resultType = long.class)
long insertImage(byte[] content);
The way I am trying to use it:
byte[] fileContent = IOUtils.toByteArray(inputStream);
long id = imageDao.insertImage(fileContent);
The exception what I get:
java.lang.ClassCastException: java.lang.Long cannot be cast to [B
at org.apache.ibatis.type.ByteArrayTypeHandler.setNonNullParameter(ByteArrayTypeHandler.java:26)
at org.apache.ibatis.type.BaseTypeHandler.setParameter(BaseTypeHandler.java:53)
at org.apache.ibatis.scripting.defaults.DefaultParameterHandler.setParameters(DefaultParameterHandler.java:87)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.parameterize(PreparedStatementHandler.java:93)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.parameterize(RoutingStatementHandler.java:64)
at org.apache.ibatis.executor.SimpleExecutor.prepareStatement(SimpleExecutor.java:86)
at org.apache.ibatis.executor.SimpleExecutor.doUpdate(SimpleExecutor.java:49)
at org.apache.ibatis.executor.BaseExecutor.update(BaseExecutor.java:117)
at org.apache.ibatis.executor.CachingExecutor.update(CachingExecutor.java:76)
at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:198)
at org.apache.ibatis.session.defaults.DefaultSqlSession.insert(DefaultSqlSession.java:185)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
I do not want to create POJO class with getter/setter method for this one "content" param but I think this issue is related to missing POJO.
What is the solution?
EDIT
I am trying to debug mybatis code and I have found "[B" in the parameterTypes:
#SelectKey is useful when you want to reuse generated value farther in the code, but it seems yo will not.
Then why not keep everything in the SQL:
INSERT INTO image (id, content) VALUES ((SELECT NEXTVAL('image_seq')), #{content})
For exception regarding parameters, parameters must be named with #Param annotation
int insertImage(#Param("content") byte[] content);
or
int insertImage(#Param("id) Long id, #Param("content") byte[] content)
Note that INSERT as well as UPDATE and DELETE statements returns type int being the number of inserted/updated/deleted rows, [...]
EDIT: unless you consider that under the hood, the java 8 PreparedStatement.executeLargeUpdate returning long is executed.
[...] and not the generated key as it is suggested. Then it seems you eventually want to get the key value, that means back to square one with #SelectKey and need for a POJO and a target property for the generated value. It even works with bulk insert with generated keys.
I have discovered lately that actual parameters name can be used (then your code will work as is) if following instructions in settings section of the documentation:
useActualParamName Allow referencing statement parameters by their
actual names declared in the method signature. To use this feature,
your project must be compiled in Java 8 with -parameters option.
(Since: 3.4.1) valid values: true | false default: true
java.lang.Long cannot be cast to [B
This is saying that you are trying to convert long to byte[]
Looking at source of org.apache.ibatis.type.ByteArrayTypeHandler:
public void setNonNullParameter(PreparedStatement ps, int i, byte[] parameter, JdbcType jdbcType) throws SQLException {
ps.setBytes(i, parameter);
}
I think you need to remove {id} from insert annotation (as this value is autogenerated).
#Insert("INSERT INTO image (content) VALUES (#{content})")
Otherwise parameters are shifted by one.
I have a requirement to set the DB2SECURITYLABEL of a row upon inserting that row into the database via Hibernate. I've been unsuccessful thus far and I'm seeking advice on how to set the DB2SECURITYLABEL in Java and have Hibernate do the insert for me.
Inside of my entity, I have defined the following:
#Column(name="slabel") //this matches the name of the DB2SECURITYLABEL column
private byte[] slabel;
public void setSlabel(byte[] slabel){
this.slabel = slabel;
}
public byte[] getSlabel(){
return this.slabel;
}
In my controller I convert the hex string that represents my security label into a byte[], setSlabel with that byte[], and save the record through a Spring Data Repository .save(entity). Upon saving that record, the db2 driver throws an error:
SQLCODE=-20402 SQLSTATE=42519 .
This error code is indicative of a record being saved without a required DB2SECURITYLABEL.
Assumptions:
I am able to insert/select into/from this table via commandline using the same
security label. insert into schema.table (field1, slabel) values
(123,x'FFF01010101000CABBBBFFF') <-not my real label. I can select the records that I insert via commandline and in my application.
I am able to retrieve existing records with DB2SECURITYLABELS
and read the label as a byte[] and as hex string using my application.
I believe I am inserting a correct label. I have verified the byte[]
that I am applying to the object is correct. I was able to insert a
record with a valid DB2SECURITYLABEL via the commandline, and
retrieved it in my application; the two byte[] for the
DB2SECURITYLABELs are identical and convert back to the same hex
string.
Has anyone set a DB2SECURITYLABEL in a Java object as a byte[] and used Hibernate to save the data to a db2 database?
I appreciate any assistance.
String sql = "select Band.band_id bandId from guest_band Band";
sessionFactory.getCurrentSession().createSQLQuery(sql)
.addScalar("bandId", Hibernate.LONG)
.list();
I got to know that addScalar() is used to state hibernate the DataType of the selected item, bandId in this case.
But my question is, why do we need to specify the type to hibernate? What does it internally perform? Secondly is it an exception if we don't addScalar()? Lastly, is there any alternate way how this can be achieved?
It's not mandatory but would certainly help to use
From https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querysql.html
The most basic SQL query is to get a list of scalars (values).
sess.createSQLQuery("SELECT * FROM CATS").list();
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list();
These will return a List of Object arrays (Object[]) with scalar values for each column in the CATS table. Hibernate will use ResultSetMetadata to deduce the actual order and types of the returned scalar values.
To avoid the overhead of using ResultSetMetadata, or simply to be more explicit in what is returned, one can use addScalar():
sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME", Hibernate.STRING)
.addScalar("BIRTHDATE", Hibernate.DATE)
This query specified:
the SQL query string
the columns and types to return
This will return Object arrays, but now it will not use ResultSetMetadata but will instead explicitly get the ID, NAME and BIRTHDATE column as respectively a Long, String and a Short from the underlying resultset.
This also means that only these three columns will be returned, even
though the query is using * and could return more than the three
listed columns.
It is possible to leave out the type information for all or some of the scalars.
sess.createSQLQuery("SELECT * FROM CATS")
.addScalar("ID", Hibernate.LONG)
.addScalar("NAME")
.addScalar("BIRTHDATE")
This is essentially the same query as before, but now ResultSetMetaData is used to determine the type of NAME and BIRTHDATE, where as the type of ID is explicitly specified.
How the java.sql.Types returned from ResultSetMetaData is mapped to Hibernate types is controlled by the Dialect. If a specific type is not mapped, or does not result in the expected type, it is possible to customize it via calls to registerHibernateType in the Dialect.
An easy example what addScalar is used for:
public byte[] getFile(Integer id){
Query q = session
.createSQLQuery("select some_file from tbl_name where id=:id")
.addScalar("some_file", StandardBasicTypes.BINARY);
q.setInteger("id", id);
return (byte[]) q.uniqueResult();
}
For example you have blob data type in your database, in this case you can easily cast your result into byte[] but if you run the query without the addScalar function you will get your result as a blob and you can't cast blob to byte[] directly, you need to write a code for conversion:
try{
Blob blob =(Blob)q.uniqueResult();
int blobLength = (int) blob.length();
byte[] blobAsBytes = blob.getBytes(1, blobLength);
return blobAsBytes;
} catch (Exception e) {
return null;
}
In this problem, it's much easier to use addScalar.
As far as I now, it is not necessary. I've never, ever, written a query with .addScalar.
You could simply replace that with something like:
Query q = sessionFactory.getCurrentSession().createQuery(
"select b.band_id " +
"from guest_band as b "
);
List idList = q.list();
Although this may depend on how your entities are set up, it should work.
Perhaps .createSQLQuery and .createQuery are different in this manner.
Refer to this post on what the .addScalar() actually does: What does addScalar do?
Edit: I am familiar with Java, and I guess I was assuming you were using Java for your post. If using C# this may be different.
MSSQL has a great feature called Table Valued Parameters. It allows you to pass a table of a custom data to stored procedures and functions.
I was wondering what is the equivalent in PostgreSQL, if one exists, using JDBC?
I know about the option of passing arrays as function parameters, but that seems limited to PostgreSQL data types.
Consider the following PL/pgSQL code:
CREATE TYPE number_with_time AS(
_num float,
_date timestamp
);
and this function header:
CREATE OR REPLACE FUNCTION myfunc(arr number_with_time[])
Can anyone post a Java code using JDBC driver of calling that function with an array of the user defined data type?
Assuming you want to pass values from the client. If the values exist in the database already there are other, simpler ways.
Syntax for array of composite_type
I know about the option of passing arrays as function parameters, but
that seems limited to PostgreSQL data types.
What you can pass seems to be limited by Java Types and JDBC Types, and there does not seem be provisions for array types, not to speak of arrays of composite values ...
However, you can always pass a text representation. I am building on two basic facts:
Quoting the manual:
Arrays of any built-in or user-defined base type, enum type, or
composite type can be created. Arrays of domains are not yet supported.
Bold emphasis mine. Therefore, after you have created the type number_with_time as defined in your question, or defined a table with the same columns which registers the row type in the system automatically, you can also use the array type number_with_time[].
There is a text representation for every value.
Therefore, there is also a text representation for number_with_time[]:
'{"(1,2014-04-20 20:00:00)","(2,2014-04-21 21:00:00)"}'::number_with_time[]
Function call
The actual function call depends on the return values defined in your function - which is hidden in your question.
To avoid complications from array handling in JDBC, pass the text representation. Create the function taking a text parameter.
I am not going to use the name "date" for a timestamp. Working with this slightly adjusted type definition:
CREATE TYPE number_with_time AS(
_num float
, _ts timestamp
);
Simple SQL function:
CREATE OR REPLACE FUNCTION myfunc_sql(_arr_txt text)
RETURNS integer -- example
LANGUAGE sql AS
$func$
SELECT sum(_num)::int
FROM unnest (_arr_txt::number_with_time[]) x
WHERE _ts > '2014-04-19 20:00:00';
$func$;
Call:
SELECT myfunc_sql('{"(1,2014-04-20 20:00:00)","(2,2014-04-21 21:00:00)"}');
db<>fiddle here
Old sqlfiddle
Demonstrating:
above SQL function
PL/pgSQL variant
a couple of syntax variants for the array of composite type
the function calls
Call the function like any other function taking a simple text parameter:
CallableStatement myProc = conn.prepareCall("{ ? = call myfunc_sql( ? ) }");
myProc.registerOutParameter(1, Types.VARCHAR);
// you have to escape double quotes in a Java string!
myProc.setString(2, "{\"(1,2014-04-20 20:00:00)\",\"(2,2014-04-21 21:00:00)\"}");
myProc.execute();
String mySum = myProc.getInt(1);
myProc.close();
Details in the Postgres JDBC manual here.
Example to return a whole table via JDBC:
Return rows from a PL/pgSQL function
Try something like this:
------------------ your connection
V
Array inArray = conn.createArrayOf("integer", new Integer[][] {{1,10},{2,20}});
stmt.setArray(1, inArray);
A sample method you could use to build your test:
public void testInsertMultiDimension() throws Exception {
Connection c = getConnection();
PreparedStatement stmt = c.prepareStatement("INSERT INTO sal_emp VALUES ('multi_Bill',?,?);");
Array intArray = c.createArrayOf("integer", new Integer[] {1000,1000,1000,1000});
String[][] elements = new String[2][];
elements[0] = new String[] {"meeting_m","lunch_m"};
elements[1] = new String[] {"training_m","presentation_m"};
//Note - although this is a multi-dimensional array, we still supply the base element of the array
Array multiArray = c.createArrayOf("text", elements);
stmt.setArray(1, intArray);
stmt.setArray(2, multiArray);
stmt.execute();
//Note - free is not implemented
//myArray.free();
stmt.close();
c.close();
}
Helpful links:
Binding parameter as PostgreSQL array
Postgres and multi-dimensions arrays in JDBC
Passing Array from Java to Postgres
Your problem is PostgreSQL can use table or complex type as function's parameter or "table or complex type"'s array as function's paramter?
postgresql all support. and when you create a table, it's auto create an complex type named same as tablename.
like :
digoal=# create table tbl123(id int, info text);
CREATE TABLE
digoal=# select typname from pg_type where typname='tbl123';
typname
---------
tbl123
(1 row)
and you can use this type in function direct.
for exp :
digoal=# create or replace function f_tbl123(i tbl123) returns tbl123 as $$
declare
begin
return i;
end;
$$ language plpgsql;
CREATE FUNCTION
digoal=# insert into tbl123 values (1,'test'),(2,'test2');
INSERT 0 2
digoal=# select f_tbl123(t) from tbl123 t;
f_tbl123
-----------
(1,test)
(2,test2)
(2 rows)
the array is also can used in postgresql function.
if you don't known how array construct in java, i think this exp can help you.
digoal=# select (unnest('{"(1,abc)","(2,ww)"}'::tbl123[])).*;
id | info
----+------
1 | abc
2 | ww
(2 rows)
digoal=# select '{"(1,abc)","(2,ww)"}'::tbl123[];
tbl123
----------------------
{"(1,abc)","(2,ww)"}
(1 row)
digoal=# select array['(1,abc)','(2,ww)'];
array
----------------------
{"(1,abc)","(2,ww)"}
(1 row)
digoal=# select array['(1,abc)','(2,ww)']::tbl123[];
array
----------------------
{"(1,abc)","(2,ww)"}
(1 row)
digoal=# select (unnest(array['(1,abc)','(2,ww)'])::tbl123).*;
id | info
----+------
1 | abc
2 | ww
(2 rows)