ALL>
I have here a named query to update and replace records.
#NamedQuery(name = POST.UPDATE_POST_MESSAGE, query = "UPDATE Post p SET p.message = REPLACE(p.message, :value, 'ANONYMOUS')"
I wanted the "old string" to be parameterized but it shows an error of
Caused by: java.lang.IllegalArgumentException: You have attempted to set a parameter value using a name of value that does not exist in the query string UPDATE Post p SET p.message = REPLACE(p.message, :value, 'ANONYMOUS').
here's the code in my dao layer:
private static final String VALUE = "value";
public void updateMessage(String value) {
EntityManager entityManager = createEntityManager();
entityManager.createNamedQuery(POST.UPDATE_POST_MESSAGE)
.setParameter(VALUE, value)
.executeUpdate();
}
I am not sure if we can use a parameter inside the replace function, been searching everywhere and i cant find an answer.
If not possible, can someone help/recommend a way to replace records using a parameter.
Here you assigned parameter value to VALUE variable but using value((lower case ).
I'm having an issue getting my variable to work in my #Query. Below is the code
#Query("SELECT new User(userId, userEmail, userForename, userSurname, userMiddleName) "
+ "FROM User "
+ "ORDER BY :orderBy DESC")
public List<User> findAllBy(#Param("orderBy") String orderBy);
I know the variable I want is being passed in properly. At the moment this is just returning all the results ordered by userId. If I hard code the value which is being passed into this function then it correctly returns the results ordered by userEmail (which is what is being passed in). Any suggestions would be great.
Unfortunately, you can pass the parameters only to "conditional" clauses (like "where") due to underlying JDBC restrictions.
Instead of it consider to use overloaded PageRequest "of" method.
public static PageRequest of(int page,
int size,
Sort.Direction direction,
String... properties)
I can't wrap my head around how should i get my data without much boilerplate.
The problem:
I have a database that i cannot alter. Which has multiple field of same type almost same for example i have text_en and text_fr (both are the same word in different language English and French) and i got + 71 same field but different languages.
What I need is something like
#Entitiy(tableName = "blabla")
class X {
private String textTarget;
private String textMain;
...
}
How should I do my Dao interface to get desired language and map into x class
what should work is to update entity ColumnInfo(name ="text_en") for example.
#Query("select :main , :target from phrases where :id ")
List<X> getPhrase(String main,String target);
usage : getPhrase("text_en","text_esp");
// for example returning object X with field main = "hello" and target " holla")
The above example return the following error:
error: Not sure how to convert a Cursor to this method's return type
What you put in #Query is an SQL-statement, you can actually test them in sqlite command line utility or any desktop software to verify their correctness. So if you want a translation to the desired language, it should look like this:
#Query("SELECT :main AS text_main, :target AS text_target FROM `phrases` WHERE id = :id)
List<Translation> getTranslationById(String firstLang, String secondLang, long id);
Where Translation should be something like this:
class Translation {
#ColumnInfo("text_main")
String main;
#ColumnInfo("text_target")
String target;
//setters, getters, etc
}
This class is used only as a return value from the method.
I use QueryDSL predicate object with a spring REST endpoint to retrieve and query param values.
#GetMapping("/{subjectId}/students")
#RolesAllowed( {Roles.PLATFORM_ADMIN, Roles.USER})
public List<StudentResponse> getAllStudents(#PathVariable final String subjectId,
#QuerydslPredicate(root = Student.class) final Predicate predicate) {
final Predicate searchPredicate = studentPredicate()
.predicate(predicate)
.subjectId(subjectId)
.build();
return studentService.findBySubjectId(subjectId, searchPredicate);
}
student Class contains studentId and studentName attributes;
Now, if someone invokes https://hostname/{subjectId}/students?studentId=1234&studentName=test
Then above code generates the predicate object with param values. But I need to get above 2 param values out from predicate object for further processing apart from the db querying. I don't see any supportive method from predicate object to retrieve values out. So how can I do it?
There is no straightforward way to do this.
However You can try this.
predicate.toString(); ---> This would print user.Studentid=1234 && user.studentName=test
From this, you can do a string split.
Another way is with
predicate.getClass()); ----> This will give you the class name like
class com.querydsl.core.types.PredicateOperation (in case of more than one query conditions)
class com.querydsl.core.types.dsl.BooleanOperation (in case of single query condition).
With this you could typecast predicate to corresponding type and then do getArgs().
We'd like to use only annotations with MyBatis; we're really trying to avoid xml. We're trying to use an "IN" clause:
#Select("SELECT * FROM blog WHERE id IN (#{ids})")
List<Blog> selectBlogs(int[] ids);
MyBatis doesn't seem able to pick out the array of ints and put those into the resulting query. It seems to "fail softly" and we get no results back.
It looks like we could accomplish this using XML mappings, but we'd really like to avoid that. Is there a correct annotation syntax for this?
I believe the answer is the same as is given in this question. You can use myBatis Dynamic SQL in your annotations by doing the following:
#Select({"<script>",
"SELECT *",
"FROM blog",
"WHERE id IN",
"<foreach item='item' index='index' collection='list'",
"open='(' separator=',' close=')'>",
"#{item}",
"</foreach>",
"</script>"})
List<Blog> selectBlogs(#Param("list") int[] ids);
The <script> element enables dynamic SQL parsing and execution for the annotation. It must be very first content of the query string. Nothing must be in front of it, not even white space.
Note that the variables that you can use in the various XML script tags follow the same naming conventions as regular queries, so if you want to refer to your method arguments using names other than "param1", "param2", etc... you need to prefix each argument with an #Param annotation.
I believe this is a nuance of jdbc's prepared statements and not MyBatis. There is a link here that explains this problem and offers various solutions. Unfortunately, none of these solutions are viable for your application, however, its still a good read to understand the limitations of prepared statements with regards to an "IN" clause. A solution (maybe suboptimal) can be found on the DB-specific side of things. For example, in postgresql, one could use:
"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"
"ANY" is the same as "IN" and "::int[]" is type casting the argument into an array of ints. The argument that is fed into the statement should look something like:
"{1,2,3,4}"
Had some research on this topic.
one of official solution from mybatis is to put your dynamic sql in #Select("<script>...</script>"). However, writing xml in java annotation is quite ungraceful. think about this #Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
#SelectProvider works fine. But it's a little complicated to read.
PreparedStatement not allow you set list of integer. pstm.setString(index, "1,2,3,4") will let your SQL like this select name from sometable where id in ('1,2,3,4'). Mysql will convert chars '1,2,3,4' to number 1.
FIND_IN_SET don't works with mysql index.
Look in to mybatis dynamic sql mechanism, it has been implemented by SqlNode.apply(DynamicContext). However, #Select without <script></script> annotation will not pass parameter via DynamicContext
see also
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
org.apache.ibatis.scripting.xmltags.DynamicSqlSource
org.apache.ibatis.scripting.xmltags.RawSqlSource
So,
Solution 1: Use #SelectProvider
Solution 2: Extend LanguageDriver which will always compile sql to DynamicSqlSource. However, you still have to write \" everywhere.
Solution 3: Extend LanguageDriver which can convert your own grammar to mybatis one.
Solution 4: Write your own LanguageDriver which compile SQL with some template renderer, just like mybatis-velocity project does. In this way, you can even integrate groovy.
My project take solution 3 and here's the code:
public class MybatisExtendedLanguageDriver extends XMLLanguageDriver
implements LanguageDriver {
private final Pattern inPattern = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
Matcher matcher = inPattern.matcher(script);
if (matcher.find()) {
script = matcher.replaceAll("(<foreach collection=\"$1\" item=\"__item\" separator=\",\" >#{__item}</foreach>)");
}
script = "<script>" + script + "</script>";
return super.createSqlSource(configuration, script, parameterType);
}
}
And the usage:
#Lang(MybatisExtendedLanguageDriver.class)
#Select("SELECT " + COLUMNS + " FROM sometable where id IN (#{ids})")
List<SomeItem> loadByIds(#Param("ids") List<Integer> ids);
I've made a small trick in my code.
public class MyHandler implements TypeHandler {
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
Integer[] arrParam = (Integer[]) parameter;
String inString = "";
for(Integer element : arrParam){
inString = "," + element;
}
inString = inString.substring(1);
ps.setString(i,inString);
}
And I used this MyHandler in SqlMapper :
#Select("select id from tmo where id_parent in (#{ids, typeHandler=ru.transsys.test.MyHandler})")
public List<Double> getSubObjects(#Param("ids") Integer[] ids) throws SQLException;
It works now :)
I hope this will help someone.
Evgeny
Other option can be
public class Test
{
#SuppressWarnings("unchecked")
public static String getTestQuery(Map<String, Object> params)
{
List<String> idList = (List<String>) params.get("idList");
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM blog WHERE id in (");
for (String id : idList)
{
if (idList.indexOf(id) > 0)
sql.append(",");
sql.append("'").append(id).append("'");
}
sql.append(")");
return sql.toString();
}
public interface TestMapper
{
#SelectProvider(type = Test.class, method = "getTestQuery")
List<Blog> selectBlogs(#Param("idList") int[] ids);
}
}
In my project, we are already using Google Guava, so a quick shortcut is.
public class ListTypeHandler implements TypeHandler {
#Override
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, Joiner.on(",").join((Collection) parameter));
}
}
In Oracle, I use a variant of Tom Kyte's tokenizer to handle unknown list sizes (given Oracle's 1k limit on an IN clause and the aggravation of doing multiple INs to get around it). This is for varchar2, but it can be tailored for numbers (or you could just rely on Oracle knowing that '1' = 1 /shudder).
Assuming you pass or perform myBatis incantations to get ids as a String, to use it:
select #Select("SELECT * FROM blog WHERE id IN (select * from table(string_tokenizer(#{ids}))")
The code:
create or replace function string_tokenizer(p_string in varchar2, p_separator in varchar2 := ',') return sys.dbms_debug_vc2coll is
return_value SYS.DBMS_DEBUG_VC2COLL;
pattern varchar2(250);
begin
pattern := '[^(''' || p_separator || ''')]+' ;
select
trim(regexp_substr(p_string, pattern, 1, level)) token
bulk collect into
return_value
from
dual
where
regexp_substr(p_string, pattern, 1, level) is not null
connect by
regexp_instr(p_string, pattern, 1, level) > 0;
return return_value;
end string_tokenizer;
You could use a custom type handler to do this. For example:
public class InClauseParams extends ArrayList<String> {
//...
// marker class for easier type handling, and avoid potential conflict with other list handlers
}
Register the following type handler in your MyBatis config (or specify in your annotation):
public class InClauseTypeHandler extends BaseTypeHandler<InClauseParams> {
#Override
public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {
// MySQL driver does not support this :/
Array array = ps.getConnection().createArrayOf( "VARCHAR", parameter.toArray() );
ps.setArray( i, array );
}
// other required methods omitted for brevity, just add a NOOP implementation
}
You can then use them like this
#Select("SELECT * FROM foo WHERE id IN (#{list})"
List<Bar> select(#Param("list") InClauseParams params)
However, this will not work for MySQL, because the MySQL connector does not support setArray() for prepared statements.
A possible workaround for MySQL is to use FIND_IN_SET instead of IN:
#Select("SELECT * FROM foo WHERE FIND_IN_SET(id, #{list}) > 0")
List<Bar> select(#Param("list") InClauseParams params)
And your type handler becomes:
#Override
public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {
// note: using Guava Joiner!
ps.setString( i, Joiner.on( ',' ).join( parameter ) );
}
Note: I don't know the performance of FIND_IN_SET, so test this if it is important
I had done this with postgresql.
#Update('''
UPDATE sample_table
SET start = null, finish = null
WHERE id=ANY(#{id});
''')
int resetData(#Param("id") String[] id)
ANY works like the IN.
Code above is using groovy but can be converted into java by replacing the single quotes into double.