I have a code that creates sql parameters using MapSqlParameterSource. Here is my code:
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue(EVENT_ID, eventId)
.addValue(TYPE, type.toString())
.addValue(ACCOUNT_ID, null)
.addValue(USER_ID, null);
if (Type.SPOOFER_USER == type) {
parameters.addValue(USER_ID, account.getUser().getId());
}
else {
parameters.addValue(ACCOUNT_ID, account.getId());
}
Basically, if account type is spoofer, I have to have user id instead of account id. However, I don't like that I have to set account_id and user_id to null when I instantiate parameters. Is there way to set account_id and user_id as null so I don't have to write this two lines?:
MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue(EVENT_ID, eventId)
.addValue(TYPE, type.toString())
.addValue(ACCOUNT_ID, null) //////////////////////////THIS ONE
.addValue(USER_ID, null); //////////////////////////AND THIS ONE
Here is my sql query:
INSERT INTO database (id, event_id, type, account_id, user_id)
VALUES (database.nextval, :event_id, :type, :account_id, :user_id)
Update:
Maybe my question was not specific enough. What happens is that when I run
jdbcTemplate.update(insertEventExtra, parameters);
With the given parameters without making them "NULL", I get this exception in my unit test:
org.springframework.dao.InvalidDataAccessApiUsageException: No value supplied for the SQL parameter 'user_id': No value registered for key 'user_id'
I use hsql to test it. my .sql looks like this:
...
ID NUMBER(38,0) PRIMARY KEY,
EVENT_ID BIGINT NOT NULL,
TYPE VARCHAR2(20 BYTE) NOT NULL,
ACCOUNT_ID NUMBER(38,0),
GROUP_ID NUMBER(38,0),
USER_ID NUMBER(38,0),
...
So my specific question is that my test is giving me exception when I try to run test with parameters without setting them to null.
You must include the addValue(ACCOUNT_ID, null) and addValue(USER_ID, null) because your INSERT statement includes the two named parameters :account_id, :user_id.
The framework attempts to extract the values for the named parameters from the MapSqlParameterSource object and when it does not find one of them, it throws the exception. It does this to avoid user errors, because if you didn't intend to provide a value for a parameter, you wouldn't include the parameter in the INSERT statement.
Make your columns nullable and default to null in the database schema. Then, if you don't specify a value for a column when inserting, it should default to null
By default all not null columns have default value of NULL unless and until you provide value while inserting or updating the column.
Related
I call a stored procedure using JPA native query and PostgreSQL DB.
#Repository:
#Modifying
#Query(value="call proc(?1,?2,?3,?4,?5)",nativeQuery = true)
void saveProc(#Param("reply_by") Long replyBy,
#Param("reply_to") Long replyTo,
#Param("reply") String reply,
#Param("status"),
#Param("app_id") long appId);
As some of the parameter are null (foreign key).
ServiceImpl:
repo.saveProc(2,null,"dvvf",null,null) // java null
I'm getting this error:
procedure proc(bigint, bytea, character varying, bytea,bytea) does not exist
Hint: No procedure matches the given name and argument types. You might need to add explicit type casts.
How can I pass null values instead of bytea?
You have to cast the parameter that could be null because Hibernate does not know the parameter type: #Query(value="call proc(cast(?1 as bigint),cast(?2 as bigint),?3,cast(?4 as bigint),cast(?5 as bigint))",nativeQuery = true)
I've the following schema :
CREATE TABLE API
(
ID BIGINT NOT NULL,
NAME VARCHAR NOT NULL,
API_PARENT_ID BIGINT,
CONSTRAINT API_PKEY PRIMARY KEY (ID)
CONSTRAINT API_PARENT_ID_FKEY FOREIGN KEY (API_PARENT_ID) references API,
);
My API entity have a FK on itself in case of api dependencies (like child-parent relation)
I've handle myself (meaning I've avoided to use hibernate) the CRUD part with a jdbc template.
I've encountering a problem when my API is the parent and then the api_parent_id value should be null.
The following line (i've skipped the meaningless columns):
String _SQL_UPDATE_API = "update api set api_parent_id = ? where id = ?"
this.jdbcTemplate.update(_SQL_UPDATE_API, api.getParent !=null ? api.getParent().getId() : null , api.getId());
return the following error :
[90012-196]; nested exception is org.h2.jdbc.JdbcSQLException: Parameter "#1" is not set; SQL statement: update api set api_parent_id = ? where id = ?
How should I handle the null value ?
I've tried with MapSqlParameterSource or with a PreparedStatement in specifying the
ps.setNull(1,Types.NULL);
ps.setNull(1,Types.NULL);
Unfortunately, you'll need to set the specific JDBC type of the column that corresponds to the type of the column, according to your JDBC driver.
Some JDBC drivers are pickier than other ones. I guess H2 needs the specific type.
In this case, use Types.BIGINT, as in:
ps.setNull(1,Types.BIGINT);
Pass the argument along with arguments type using this update function.
Integer id = 1
Integer parent_id = null
String _SQL_UPDATE_API = "update api set api_parent_id = ? where id = ?";
java.lang.Object[] args = [parent_id, id]
int[] argTypes = [java.sql.Types.BIGINT, java.sql.Types.BIGINT]
updCnt = jdbcTemplate.update(_SQL_UPDATE_API, args, argTypes);
Note - this is Groovy syntax, so adjust for Java if required.
Worked for my in Oracle 12.
Suppose I hava a table with a column named status whose type is tinyint(1), and I use the following jdbc query to get a specific row from database, but now the queryForMap() method here will auto convent the tinyint(1) status field to the type boolean, but in fact I'm not only going to use this field to save 0 or 1 (also store value 2 for example), so I would prefer this type of status to be byte. How can I achieve this goal?
Map<String, Object> task = jdbcTemplate
.queryForMap("select * from task where id = ? and deleted = 0 for update", taskId);
Thanks in advance.
MySQL uses tinyint(1) as an alias of boolean. So driver of mysql will treat tinyint(1) as boolean value. You can disable this future by adding tinyInt1isBit=false to your connection url (as description in the link above). After that you will get Integer of the tinyint(1) column. But I suggest obey the alias, and define your table as tinyint or tinyint(4) to identify it's not boolean.
My tables
N
ID|T_ID
1|1
2|2
T
ID|NAME
1|T1
2|T2
Using the tables as follows
com.db.N N_TABLE = N.as("N_TABLE");
com.db.T T_TABLE = T.as("T_TABLE");
com.db.T T2_TABLE = T.as("T2_TABLE"); //Random alias, not used in query
SelectQuery selectQuery = create.selectQuery();
selectQuery.addFrom(N_TABLE);
selectQuery.addJoin(T_TABLE, JoinType.LEFT_OUTER_JOIN, T_TABLE.ID.eq(N_TABLE.T_ID));
Result<Record> result = selectQuery.fetch();
for (Record record : result) {
System.out.println(record.get(T2_TABLE.NAME));
}
It gives a ambiguity warning, but still gets the value even though alias is wrong. I would expect it to return "null", I guess it falls back to using only field name.
Any idea how should I use it to get "null" in case of a wrong alias?
EDIT
I'll try to provide a more concrete example
My table is as follows
CREATE TABLE user
(
id bigserial NOT NULL,
username character varying(200) NOT NULL,
last_name character varying(100),
created_user_id bigint NOT NULL,
modified_user_id bigint NOT NULL,
CONSTRAINT pk_user PRIMARY KEY (id),
CONSTRAINT user_username_key UNIQUE (username)
)
Data in tables
3;"admin";"admin";3;3
4;"test";"test";4;3
Code
//Input params
Long userId = 4L;
boolean includeModifiedUser = false;
User userTable = USER.as("userTable");
User modifiedUserTable = USER.as("modifiedUserTable");
SelectQuery selectQuery = create.selectQuery();
selectQuery.addFrom(userTable);
//In some cases I want to include the last modifier in the query
if (includeModifiedUser) {
selectQuery.addJoin(modifiedUserTable, JoinType.LEFT_OUTER_JOIN, modifiedUserTable.ID.eq(userTable.MODIFIED_USER_ID));
}
selectQuery.addConditions(userTable.ID.eq(userId));
Record record = selectQuery.fetchOne();
System.out.println(record.get(userTable.LAST_NAME)); //prints "test1"
System.out.println(record.get(modifiedUserTable.LAST_NAME)); //prints "test1", would expect null as modifiedUserTable is currently not joined
Tested on jooq 3.9.3 and 3.9.5
Works as designed
In SQL, there is no such thing as a qualified column name in a result set. Instead, a result set (like any other table) has a set of columns and each column has a name, which is described by jOOQ's Field.getName(). Now, "unfortunately", in top-level SELECT statements, you are allowed to have duplicate column names, in all SQL dialects, and also in jOOQ. This is useful when you join two tables and both tables have e.g. an ID column. That way, you don't have to rename each column just because an ambiguity arises.
If you do have duplicate column names in a table / result, jOOQ will apply the algorithm described in TableLike.field(Field)
This will return:
A field that is the same as the argument field (by identity comparison).
A field that is equal to the argument field (exact matching fully qualified name).
A field that is equal to the argument field (partially matching qualified name).
A field whose name is equal to the name of the argument field.
null otherwise.
If several fields have the same name, the first one is returned and a warning is logged.
As you can see, the rationale here is that if there is no full or partial identity or qualified name equality between a field in the result set and the field you're looking up in the result set, then the field name as in Field.getName() is used to look up the field.
Side note on column match ambiguity
At first, you've mentioned that there was an "ambiguous match" warning in the logs, which then disappeared. That warning is there to indicate that two columns go by the same Field.getName(), but neither of them is an "exact" match as described before. In that case, you will get the first column as a match (for historic reasons), and that warning, because that might not be what you wanted to do.
I want to insert the default value from database when my field is null. I use an Oracle Database.
CREATE TABLE "EMPLOYEE"
("COL1" VARCHAR2(800) NOT NULL ENABLE,
"COL2" VARCHAR2(100) DEFAULT NOT NULL 'toto',
CONSTRAINT "PK_EMPLOYEE" PRIMARY KEY ("COL1")
with a simple SQL request, we can write:
insert into EMPLOYEE(COL1,COL2) values ('titi', default)
How can i do this with annotations MyBatis in Spring? I must create an HandlerType?
In mapper XML, build dynamically the SQL (add the col2 column and value when not null):
insert into employee (col1<if test="col2 != null">, col2</if>)
values (#{COL1}<if test="col2 != null">, #{col2}</if>)
EDIT: since value in annotation must be constant, I used to think dynamic SQl was not possible in annotation, but there is a trick I have found here: How to use dynamic SQL query in MyBatis with annotation(how to use selectProvider)? and checked it myself.
To use dynamic SQL this into an annotation, surround it with "script" tags:
#Insert("<script>insert into employee (col1<if test='col2 != null'>, col2</if>)
values (#{COL1}<if test='col2 != null'>, #{col2}</if>)</script>")
In tests, just escape double quotes " or replace them with simple quotes '
It should work if you omit the COL2 in your colum definition of the insert statement. Because the DB recognizes, that there is no value for the new row and it will apply the default value from the create table statement.
Have you tried something like this?
public interface EmployeeDAO {
String INSERT = "insert into employee (col1) values (#{COL1})";
#Insert(INSERT)
public int insertDefault(PersonDO p) throws Exception;
}