I have:
<delete id="deleteTaskScheduling" parameterType="List">
DELETE FROM ct_task_scheduling WHERE taskid IN
<foreach collection="list" item="taskid" open="(" close=")" index="index" separator=",">
#{taskid}
</foreach>
</delete>
and my POJO has taskid
#Data
#NoArgsConstructor
public class TaskDTO implements Serializable {
private String taskid;
...
}
and I am passing List of TaskDTO to the interface:
List <TaskDTO> taskDTOs = new ArrayList <> ();
...
taskDAO.deleteTaskScheduling(taskDTOs);
but I am getting this exception:
java.lang.IllegalStateException: Type handler was null on parameter mapping for property '__frch_taskid_0'. It was either not specified and/or could not be found for the javaType (com.mycompany.DTO.TaskDTO) : jdbcType (null) combination.] with root cause
Can someone help me? Thanks
In <foreach />, the variable specified in item represents each element of the list which is TaskDTO in your case.
<delete id="deleteTaskScheduling">
DELETE FROM ct_task_scheduling WHERE taskid IN
<foreach collection="list" item="x" open="(" close=")" separator=",">
#{x.taskid}
</foreach>
</delete>
Please read the documentation for the details.
https://mybatis.org/mybatis-3/dynamic-sql.html#foreach
Related
<mapper namespace = "com.my">
<delete id="deleteById">
DELETE FROM table
WHERE id = #{id}
</delete>
</mapper>
Code above.
Where does Mybatis get the value of #{id},must I code a resultMap in the namespace?
I am not sure what is com.my Assuming there is class named MyClass under package com.my
On your DAO I believe you have use something like
session.delete("deleteById", com.my.MyClass object);
and I believe your class com.my.MyClass have something like
private String id;
and your xml would be
<delete id="deleteById" parameterType="com.my.MyClass">
DELETE FROM table
WHERE id = #{id}
</delete>
One possible way:
Assume MyClass.xml as below:
<mapper namespace = "com.my.MyClass">
<delete id="deleteById">
DELETE FROM table
WHERE id = #{id}
</delete>
</mapper>
Then
public class MyClass{
public int deleteById( #Param("id") int id );
}
select * from users where id in ()
The query is shown above.
<select id="getByIds" resultMap="BaseResultMap">
SELECT
<include refid="BaseColumnList"/>
FROM users
WHERE id IN
<foreach item="id" collection="ids"
open="(" separator="," close=")">
#{id}
</foreach>
</select>
If the Param ids is empty, Mybatis will throw BadSqlGrammarException, which generate a query like 'select * from users where id in'.
How can I skip query and return empty list if ids is empty?
How can I skip query and return empty list if ids is empty?
To skip the query (not execute it), just don't call Mybatis.
The calling code should check if ids is empty:
return null == ids || ids.isEmpty() ? new ArrayList<User>() : session.select("getByIds", ids);
This is exactly what is asked in the question.
If you really want Mybatis to handle this, then produced query must be valid because must be executed (then not skipped) to return empty result quickly. that means forget something like id = <!-- a value that will never exist in the table --> because it could surely involve a (free and useless) full scan to search the unexisting value.
Then:
WHERE
<choose>
<when test="ids==null || ids.isEmpty()">
1 = 0 <!-- a test returning false, to adapt depending on you DB vendor -->
</when>
<otherwise>
id IN <foreach item="id" collection="ids" open="(" separator="," close=")">#{id}</foreach>
</otherwise>
</choose>
Another option to confirm would consist in using interceptors to "cancel" the query before its execution, but this is definitely overkill complexity for what has to be achieved here.
java code function
List<ApiPriceChlogEntity> getApiAndDevPrice(#Param("apiKeys") List<String> currentApiKey, #Param("devKeys") List<String> currentDevKey, #Param("startDate") Date startDate);
the mapper file
<select id="getApiAndDevPrice" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List"/>
FROM t_api_price_chlog tab1
<where>
<if test="apiKeys.size() > 0">
tab1.api_key IN
<foreach collection="apiKeys" item="item" separator="," open="(" close=")" index="">
#{item}
</foreach>
</if>
<if test="devKeys.size() > 0">
AND tab1.dev_key IN
<foreach collection="devKeys" item="item" separator="," open="(" close=")" index="">
#{item}
</foreach>
</if>
<if test="startDate != null">
AND tab1.change_date >= #{startDate}
</if>
</where>
I have test it,hope to help u.
Using test:
<select id="getByIds" resultMap="BaseResultMap">
SELECT
<include refid="BaseColumnList"/>
FROM users
<if test="ids!= null">
WHERE id IN
<foreach item="id" collection="ids"
open="(" separator="," close=")">
#{id}
</foreach>
</if>
</select>
Use mybatis Interceptor, create a nil PreparedStatement Object and return it.
#Intercepts({#Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}),
#Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
#Slf4j
public class MyBatisInterceptor implements Interceptor {
#Override
public Object intercept(Invocation invocation) throws Throwable {
//...your code
//return invocation.proceed();
return new NullExecutor();
}
}
MyBatis wants to get a PreparedStatement Object as a return value, the "NullExecutor" is:
public class NullExecutor implements PreparedStatement {}
Nothing todo, just write few thing, like:
#Override
public void setDouble(int parameterIndex, double x) throws SQLException {
//empty here
}
#Override
public boolean execute() throws SQLException {
//it's ok,do noting.
return false;
}
...etc
Add your configuration:
#Bean
public MyBatisInterceptor myBatisInterceptor() {
return new MyBatisInterceptor();
}
You need to get the "BoundSql" in Interceptor, then you can get sql and args, just google it.
I'm not sure it can work, just for learning.
With this query, I succeed to retrieve a phone number in database:
import java.util.List;
import org.springframework.data.jpa.repository.JpaReposit ory;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import com.mc.appcontacts.domain.hibernate.Contact;
public interface ContactRepository extends JpaRepository<Contact, Integer> {
#Query("SELECT c.phoneNumber from Contact c WHERE LOWER(c.name) = LOWER(:name)")
String find(#Param("name") String name);
But is it possible to specify dynamically name of the property i want to retrieve in parameter?
In all tuto i've read on the net, i learn we can pass the value of the property in parameter (In my exemple : #Param("name") String name )
but what i want to pass in parameter is the name of the property not the value !
I know the exemple below is not correct but it's to give the general idea :
#Query("SELECT c.(property) from Contact c WHERE LOWER(c.name) = LOWER(:name)")
String find(#Param("name") String name, #Param("property") String property);
With property = phoneNumber (or an other property of my table).
Thank you for your help !!
I don't understand how to do that (everything is new for me):
I have read (and try) that jpql is defined like this :
import com.mysema.query.jpa.impl.JPAQuery;
import com.mc.appcontacts.repository.ContactRepository; // managed by spring data
//jpa repository
public class ServicesC {
#Autowired
private ContactRepository repository;
#PersistenceContext // try
private EntityManager em; // try
JPAQuery query = new JPAQuery(em); // try
public Contact getOne(Integer id) {
return repository.findOne(id);
}
public String findAtt(String att, String key){
String jpql = "SELECT c." + att + " from Contact c WHERE LOWER(c.name) = LOWER(:key)"; // try
List<Contact> l = (List<Contact>) em.createQuery(jpql); // try
return "test";
}
}
But it doesn't work (i'm not surprised...) :
2014-02-24 18:18:34.567:WARN::Nested in org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'appMapping': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.mc.appcontacts.service.ServiceC com.mc.appcontacts.mvc.MappingService.service; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'Service' defined in file [C:\Professional\Workspaces\Eclipse\ContactMain\ContactCore\target\classes\com\mc\appcontacts\service\ServiceC.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [com.mc.appcontacts.service.ServiceC]: Constructor threw exception; nested exception is java.lang.NullPointerException:
java.lang.NullPointerException
at com.mysema.query.jpa.impl.JPAProvider.get(JPAProvider.java:72)
at com.mysema.query.jpa.impl.JPAProvider.getTemplates(JPAProvider.java:80)
at com.mysema.query.jpa.impl.JPAQuery.<init>(JPAQuery.java:46)
Must i define a second EntityManager only for jpql ? (Is it possible ? is it the right way ? I don't think so...)
I have already a EntityManager defin for Spring-data in xml file :
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- Activate Spring Data JPA repository support -->
<jpa:repositories base-package="com.mc.appcontacts.repository" />
<!-- Declare a JPA entityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceXmlLocation" value="classpath:META-INF/contacts/hibernate/persistence.xml" />
<property name="persistenceUnitName" value="hibernatePersistenceUnit" />
<!-- <property name="dataSource" ref="dataSource" /> -->
<property name="jpaVendorAdapter" ref="hibernateVendor" />
</bean>
<!-- Specify our ORM vendor -->
<bean id="hibernateVendor" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="${hibernate.showSql}" />
</bean>
<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
Please help me ... how does it work ?
No, it's not possible to do that. You'll have to implement it by yourself by dynamically generating the JPQL query.
Using query parameyters is not an option, because query parameters can only be values to replace in a given prepared statement, and can't alter the nature of the query itself. So you'll have to do something like
String jpql = "select c." + property + " from ...";
I think for this use case of building queries dynamically your best bet would be to explore Criteria API, which is very suitable for such things. http://docs.oracle.com/javaee/6/tutorial/doc/gjitv.html
Update 2016-06-07 - see my answer below for solution
Trying to find out if there is a way to reuse same fragment in one query.
Consider this:
<sql id="personFields">
per.id person_id,
per.created_at person_created_at,
per.email_address person_email_address,
per.first_name person_first_name,
per.last_name person_last_name,
per.middle_name person_middle_name
</sql>
The "per." alias is used to avoid column name clashing when using in queries with muiltiple joined tables.
It is included like this:
SELECT
<include refid="com.acme.data.mapper.PersonMapper.personFields"/>
FROM Person per
The problem is that it cannot be used more than once per query because we have the "per." alias.
Would be great to have something like this:
<sql id="personFields">
#{alias}.id #{alias}_person_id,
#{alias}.created_at #{alias}_person_created_at,
#{alias}.email_address #{alias}_person_email_address,
#{alias}.first_name #{alias}_person_first_name,
#{alias}.last_name #{alias}_person_last_name,
#{alias}.middle_name #{alias}_person_middle_name
</sql>
And include it like this:
SELECT
<include refid="com.acme.data.mapper.PersonMapper.personFields" alias="per1"/>,
<include refid="com.acme.data.mapper.PersonMapper.personFields" alias="per2"/>
FROM Person per1
JOIN Person per2 ON per2.parent_id = per1.id
This is currently possible (not sure since what version):
Define it:
<sql id="AddressFields">
${alias}.id ${prefix}id,
${alias}.created_at ${prefix}created_at,
${alias}.street_address ${prefix}street_address,
${alias}.street_address_two ${prefix}street_address_two,
${alias}.city ${prefix}city,
${alias}.country ${prefix}country,
${alias}.region ${prefix}region,
${alias}.sub_region ${prefix}sub_region,
${alias}.postal_code ${prefix}postal_code
</sql>
Select it:
<sql id="PurchaseSelect">
SELECT
purchase.*,
<include refid="foo.bar.mapper.entity.AddressMapper.AddressFields">
<property name="alias" value="billing_address"/>
<property name="prefix" value="billing_address_"/>
</include>,
<include refid="foo.bar.mapper.entity.AddressMapper.AddressFields">
<property name="alias" value="shipping_address"/>
<property name="prefix" value="shipping_address_"/>
</include>
FROM purchase
LEFT JOIN address billing_address ON purchase.billing_address_id = billing_address.id
LEFT JOIN address shipping_address ON purchase.shipping_address_id = shipping_address.id
</sql>
Map it:
<resultMap id="PurchaseResult" type="foo.bar.entity.sales.Purchase">
<id property="id" column="id"/>
<!-- any other purchase fields -->
<association property="billingAddress" columnPrefix="billing_address_" resultMap="foo.bar.mapper.entity.AddressMapper.AddressResult"/>
<association property="shippingAddress" columnPrefix="shipping_address_" resultMap="foo.bar.mapper.entity.AddressMapper.AddressResult"/>
</resultMap>
Unfortunately you can't do that, others have already tried (see some issues here or here). The includes are inlined and take no parameters.
One solution off the top of my head would be something like this:
<sql id="fragment">
<foreach collection="list" separator="," item="alias">
${alias}.id ${alias}_person_id,
${alias}.created_at ${alias}_person_created_at,
${alias}.email_address ${alias}_person_email_address,
${alias}.first_name ${alias}_person_first_name,
${alias}.last_name ${alias}_person_last_name,
${alias}.middle_name ${alias}_person_middle_name
</foreach>
</sql>
include it just once like:
<select id="getPersons" parameterType="java.util.List" ... >
SELECT
<include refid="fragment"/>
FROM Person per1
JOIN Person per2 ON per2.parent_id = per1.id
</select>
and have a parameterType="java.util.List" sent from the mapper interface:
public interface PersonMapper {
public List<String> getPersons(List<String> aliases);
// called with aliases = ["per1", "per2"]
}
This is ugly because your (higher level) code will have to know the aliases used inside the (lower) queries and also uses string substitutions for the fragment (${...} instead of #{...}) which can be dangerous if not handled properly... but if you can live with that...
This feature is asked to be implemented for more than 2 years (https://code.google.com/p/mybatis/issues/detail?id=652).
This static parameters in include can be found implemented in this fork: https://github.com/kmoco2am/mybatis-3
It is fully working and it has the same syntax as standard configuration parameters or static variables:
<sql id="sometable">
${prefix}Table
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
from
<include refid="sometable">
<placeholder name="prefix" value="Some"/>
</include>
</select>
Hopefully, it will be soon accepted for the main source repository.
I used mybatis-spring-1.0.3-SNAPSHOT mybatis-3.0.6 spring3.0.6.I tried to delete record from a table like this:
<delete id="deleteNote" parameterType="hashMap">
DELETE FROM BBSCS_NOTE
<where>
<if test="ids !=null and ids.length > 0">
<foreach collection="ids" item="id" open="(" close=")" separator=",">
ID IN #{id}
</foreach>
</if>
<if test="toID != null and toID != ''">AND TOID = #{toID}</if>
<if test="fromID != null and fromID != ''">AND FROMID = #{fromID}</if>
<if test="noteType != -1">AND NOTETYPE = #{noteType}</if>
</where>
</delete>
As you have seen,it's a dynamic sql.The java test code like this:
Map map = new HashMap();
String ids[] = {"1","2","3"};
map.put("ids", ids);
noteService.del(map);
When I executed java test code,there was some exception like this:
org.springframework.jdbc.UncategorizedSQLException: Error setting null parameter. Most JDBC drivers require that the JdbcType must be specified for all nullable parameters. Cause: java.sql.SQLException: Invalid column type
; uncategorized SQLException for SQL []; SQL state [null]; error code [17004]; Invalid column type; nested exception is java.sql.SQLException: Invalid column type
Why?Can you give me some advice to solve this problem?Thanks.
OK I see a few problems. First, when setting a null parameter into a Prepared Statement or a Callable Statement MyBatis needs to know the jdbc type.
Like this,
#{myNullParamenter, jdbcType=VARCHAR}
You're also generating your 'in clause incorrectly. You need to use the foreach tag to only generate list of the values. Move the "ID IN" part out of the foreach tag.
<if test="ids !=null and ids.length > 0">
ID IN
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</if>
I would also recommend against using HashMaps. The new Mapper classes are much better.
The problem is that since the 3.0.x versions the default JDBC type for null parameters is Types.OTHER which not supported by some JDBC drivers like Oracle 10g.
Here a post that explain this issue.
The solution I found is very simple, I set jdbcTypeForNull to NULL in the configuration file.
<configuration>
<properties resource="mybatis-config.properties" />
<settings>
<setting name="jdbcTypeForNull" value="NULL" />
</settings>
<environments default="development">
....
</environments>
<mappers>
....
</mappers>
</configuration>