myBatis: using typehandlers in insert statements - mybatis

Trying to use the typehandler for an insert statement in mybatis, but it is not working. I am using mybatis-spring 1.2.1, mybatis 3.2.3. But I am getting an error message saying that parameter 2 is not set.
Here is the code,
mybatis config file:
<configuration>
<typeAliases>
.......
.........
<typeAlias type="org.test.util.TSTypeHandler" alias="TSTypeHandler"/>
</typeAliases>
<typeHandlers>
.......
<typeHandler handler="TSTypeHandler" javaType="java.lang.String" jdbcType="TIMESTAMP"/>
</typeHandlers>
<mappers>
......
</mappers>
</configuration>
Mapper xml:
<insert id="saveMyOutput">
INSERT INTO TEST.MY_OUTPUT (
YEAR,
RUN_TMS,
PRODUCT
)
VALUES
<foreach item="element" index="index" collection="mOutput"
open="(" separator="),(" close=")">
#{element.year},
#{element.runTS, typeHandler=TSTypeHandler},
#{element.product}
</foreach>
</insert>

Type handlers declared in mybatis config file are intended to be applied globally, then beware of side effect, especially when common types such are java.lang.String are involved.
In this case, mybatis would apply the type handler to every string to convert it to SQL timestamp. And I suppose you want most of string params be passed as is.
First, add logs in method TSTypeHandler.setNonNullParameter to check whether it is actually called or not.
Then just remove type handler from global config and reference it only in statement.
Try without alias first: use full qualified name.

Related

MyBatis inserting null to a DB2 DATE column

Using MyBatis (Version 3.2.5) I am trying to Update a DB2 DATE column with NULL, but getting this below error
org.springframework.jdbc.UncategorizedSQLException: Error setting null for parameter #5 with JdbcType NULL . Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. Cause: com.ibm.db2.jcc.c.SqlException: [ibm][db2][jcc][10281][10295] JDBC type 0 is not yet supported.
; uncategorized SQLException for SQL []; SQL state [null]; error code [-99999]; [ibm][db2][jcc][10281][10295] JDBC type 0 is not yet supported.; nested exception is com.ibm.db2.jcc.c.SqlException: [ibm][db2][jcc][10281][10295] JDBC type 0 is not yet supported.
I tried the below suggestions, but they are not working for me
MyBatis - jdbcTypeForNull Oracle
The exception is thrown from line 39 of BaseTypeHandler.
Set a break point to check the current value of jdbcType.TYPE_CODE. It should work fine when it equals java.sql.Types.TIMESTAMP (93), that might not be the case.
So if not the case,
Expected actual type for this being DateTypeHandler.
You might then have to define your own DateTypeHandler, just extend it and override method setParameter from BaseTypeHandler, actually just replace variable jdbcType.TYPE_CODE with forced java.sql.Types.TIMESTAMP.
To use it by default for all Date parameters, register it in the typeHandler section of mybatis-config.xml (or Spring). Otherwise set the typeHandler attribute for concerned parameter in the insert statement.
Found the solution..
in mybatis-config.xml, I had
<setting name="jdbcTypeForNull" value="NULL" />
I changed it like below.
<configuration>
<settings>
<setting name="jdbcTypeForNull" value="DATE" />
</settings>
</configuration>

When to use $ vs #?

I am confused about using $ vs #. I didn't found any guides for this. I used them as
name = #{name}, name like '%${word}%', order by name ${orderAs},where name = #{word}
Sometimes , these are work fine but at the sometimes , parameters aren't included or gave me error like
org.apache.ibatis.reflection.ReflectionException: There is no getter
for property named 'name'.......
So, I'd like to know when to use $ or # ?
Following the myBatis guidelines #{} is used in your sql statements.
If you take a look any of MyBatis Reference in the Section Mapper XML Files it says explicity:
Notice the parameter notation:
#{id}
Otherwise ${} is for
1- Configuration properties.
For example:
<properties resource="org/mybatis/example/config.properties">
<property name="username" value="dev_user"/>
<property name="password" value="F2Fa3!33TYyg"/>
</properties>
Then the properties can be used like next:
<dataSource type="POOLED">
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
2- String Substitution ${} (Parameters section):
By default, using the #{} syntax will cause MyBatis to generate
PreparedStatement properties and set the values safely against the
PreparedStatement parameters (e.g. ?). While this is safer, faster and
almost always preferred, sometimes you just want to directly inject a
string unmodified into the SQL Statement. For example, for ORDER BY,
you might use something like this:
ORDER BY ${columnName}
Here MyBatis won't modify or escape the string.
NOTE It's not safe to accept input from a user and supply it to a
statement unmodified in this way. This leads to potential SQL
Injection attacks and therefore you should either disallow user input
in these fields, or always perform your own escapes and checks.
So definitively in name like '%${word}%' ororder by name ${orderAs}` you need to use String substitution not a prepared statement.
This (${} - simple variable)
SELECT * from user where usernum = ${usernum}
translates into this
SELECT * from user where usernum = 666
, but this (#{} - equivalent to PreparedStatement in JDBC)
SELECT * from user where usernum = #{usernum}
translates into
SELECT * from user where usernum = ?
, so the best usage would be something like
SELECT * from ${tablename} where name = #{name}
I was also confused with this . Then I did some research. I had a query which is something like select * from tablename h where h.id=#userid# in ibatis. Then I had to migrate it into mybatis 3 . The same statement didnt work. So I had changed it into select * from tablename h where h.id=#{userid}

MyBatis error: Caused by: java.sql.SQLSyntaxErrorException: unexpected token: < required: (

Why do I get this error from MyBatis 3?
Caused by: java.sql.SQLSyntaxErrorException: unexpected token: < required: (
This is my SQL:
SELECT * FROM GC0101.AGENT_POOL_CLIENT_ASSIGNMENT WHERE GO_CD = ?
AND ASSIGNMENT_STATUS_CD IN %lt;foreach item="item" index="index"
collection="assignmentStatusCd" open="(" separator="," close=")"%gt;
? %lt;/foreach%gt;
created from this query:
#Select("SELECT * FROM GC0101.AGENT_POOL_CLIENT_ASSIGNMENT WHERE GO_CD =
#{generalOfficeCd, jdbcType=CHAR} AND ASSIGNMENT_STATUS_CD IN " +
"<foreach item=\"item\" index=\"index\" collection=\"assignmentStatusCd\"
open=\"(\" separator=\",\" close=\")\"%gt; #{item, jdbcType=CHAR} %lt;/foreach%gt;")
If that is indeed your annotation value then it will not work because you are trying to include an XML statement inside the annotation's only value which is not parsed for XML elements, it's taken "as it is".
You can find a possible solution in this post: How to use Annotations with iBatis (myBatis) for an IN query? (see the #Select("<script>...") example in LordOfThePigs' answer - works with the latest MyBatis version) or try with a #SelectProvider annotation. See the Mybatis 3 API docs for more details and this note as an advice:
Java Annotations are unfortunately limited in their expressiveness and flexibility. Despite a lot of time spent in investigation, design and trials, the most powerful MyBatis mappings simply cannot be built with Annotations – without getting ridiculous that is.

Why the order of params of function imported from stored procedure changed?

I have a stored procedure in SQL Sever 2008 like:
ALTER PROCEDURE [dbo].[mySP]
#FirstName As varchar(30),
#LastName As varchar(30),
#ID As int
As
Begin
--......
End
then in EF imported this SP as a function like:
public ObjectResult<Nullable<global::System.Int32>> MyFunc(global::System.String LastName, global::System.String FirstName,Nullable<global::System.Int32> ID)
{
//....
}
it works fine for long time.
Then I add some new thing to EF edmx with "update from database" today, the function parameter changed! it became:
public ObjectResult<Nullable<global::System.Int32>> MyFunc(global::System.String LastName,Nullable<global::System.Int32> ID, global::System.String FirstName)
{
//....
}
It's hardto believe it. I already have many codes to call this func and it worked fine. Now all of them are not working. Even I can manually change the parameter, but maybe it back the orginal order with the generated-code!
How to resolve this problem.
This error occurs when using ALTER PROCEDURE on the stored Procedure, and does not appear in all cases.
We have been able to show that the parameter order is controlled by SQL and that after altering a stored proc parameter list (e.g. adding a parameter, especially one with a default value), then using 'Update Model From Database' on the Entity model, the parameter order becomes alphabetical. One possibility is that SQL has a mechanism for maintaining compatibility with compiled procs when an optional parameter is added, and this is manifesting as the observed behavior.
You need to fix it in the database. The only way we have been able to restore the correct parameter order is to DROP and CREATE the stored procedure, then update the model. No change to the storage model will survive the Update from database.
We are using SQL 2000.
As of EF4, default code generation is also based on a T4 template. By drilling into that T4 we can see how it generate codes for the function import:
foreach (EdmFunction edmFunction in container.FunctionImports)
{
IEnumerable<FunctionImportParameter> parameters =
FunctionImportParameter.Create(edmFunction.Parameters, code, ef);
string paramList = string.Join(", ", parameters.Select(p =>
p.FunctionParameterType + " " + p.FunctionParameterName).ToArray());
...
So it's exactly based on how your SSDL schema is look like under you model.
For example, for the uspUpdateEmployeePersonalInfo SP in Adventureworks database:
CREATE PROCEDURE [HumanResources].[uspUpdateEmployeeHireInfo]
#EmployeeID [int],
#Title [nvarchar](50),
#HireDate [datetime],
#RateChangeDate [datetime],
#Rate [money],
#PayFrequency [tinyint],
#CurrentFlag [dbo].[Flag]
The SSDL is like the below:
<Function Name="uspUpdateEmployeePersonalInfo" Aggregate="false"
BuiltIn="false" NiladicFunction="false" IsComposable="false"
ParameterTypeSemantics="AllowImplicitConversion" Schema="HumanResources">
<Parameter Name="EmployeeID" Type="int" Mode="In" />
<Parameter Name="NationalIDNumber" Type="nvarchar" Mode="In" />
<Parameter Name="BirthDate" Type="datetime" Mode="In" />
<Parameter Name="MaritalStatus" Type="nchar" Mode="In" />
<Parameter Name="Gender" Type="nchar" Mode="In" />
</Function>
And it will result in this code to be generated inside the ObjectContext:
public int UpdateEmployeePersonalInfo(Nullable<global::System.Int32> employeeID,
global::System.String nationalIDNumber,
Nullable<global::System.DateTime> birthDate,
global::System.String maritalStatus,
global::System.String gender)
My guess is that the order of the properties in your SSDL schema in your model has been changed and EF generates new codes to match that. So after validating that the parameters are declared in the desired order in you SP inside the database, try removing the SP from you model and update your model from database one more time and you'll see that the generated method code will be changed accordingly.
We are seeing this issue happen with lots of our stored procedure and we have not been able to identify why. Any new information on this will be a lot of help.
I have seen it happen on stored procedure with a lot of input and output parameters more consistently. Moreover on update form database command which when stored procedure is not changed and also edmx file is not changed but the designer causes the reorder of parameters.
Verify that the compatibility level of the database is not below 90
sp_dbcmptlevel 'your_database_name'
If it reports a value below 90 then run
sp_dbcmptlevel 'your_database_name', 90
Thanks to lajones in Codeplex

Entity Framework + Sql Anywhere 11 + Stored procedures

I have been playing with the EF in the last couple of days.
Our applications are based on SQL Anywhere 10 databases, and all our data access are done through stored procedures. Since EF is not supported by SA 10, I am testing EF with SA 11.
For this purpose i have create a small database with 2 tables and a couple of stored procedures (based on the nerddinner database from the asp.net mvc samples, see here )
I have created a model from the database, tables and stored procedures and did the necessary function imports.
I have a stored procedure with the following signature:
ALTER PROCEDURE "DBA"."get_dinner"( #dinner_id integer)
BEGIN
select dinner_id, title, event_date, description, hosted_by , contact_phone, address, country, latitude, longitude
from dba.dinners d
where d.dinner_id = #dinner_id
END
And the resulting function import code looks like this:
public global::System.Data.Objects.ObjectResult<dinner> get_dinner(global::System.Data.Objects.ObjectParameter dinner_id)
{
return base.ExecuteFunction<dinner>("get_dinner", dinner_id);
}
And that's the problem. Ideally, the code generated should accept an int parameter, instead of global::System.Data.Objects.ObjectParameter dinner_id
As far as I see, the edmx file has all the data it needs to interpret the parameter types correctly:
<Function Name="get_dinner" Aggregate="false" BuiltIn="false" NiladicFunction="false" IsComposable="false" ParameterTypeSemantics="AllowImplicitConversion" Schema="DBA">
<Parameter Name="dinner_id" Type="int" Mode="InOut" />
</Function>
Am I missing something here? What else is needed in order to have a function import with the proper parameter type? Is this something you can correct by tweaking the edmx file, or is it a problem of the SA11 EF support implementation.
Hope someone can give me some further clues.
This is a known problem with InOut parameters and code gen for Function Imports.
We have been talking about making InOut parameters produce code like this:
public ObjectResults<dinner> get_dinner(ref int dinner_id);
Rather than what you have.
One thing to try is converting from an 'InOut' to an 'In' parameter. Code gen should then produce something like this:
public ObjectResults<dinner> get_dinner(int dinner_id);
The real question though is does it work if you call it?
Hope this background helps
Cheers
Alex