Mybatis selectKey not returning sequence value - java

I'm using the below insert query in mybatis. In ibatis, the same query returned seq_consumer_id.nextval to the calling method in java, and inserted the same into the consumer_id column. But in mybatis, return value of the method is always 1 (I'm assuming its the no of rows inserted), though consumer_id column is correctly updated from sequence. Can't we generate the key, insert it and return the same to java class in mybatis ?
<insert id="insertConsumer" parameterType="com.enrollment.vo.ConsumerVO">
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select seq_consumer_id.nextval as id from dual
</selectKey>
insert into quotation_consumer (consumer_id, consumer_type, dob,
create_date, ENROLLMENT_INDICATOR, QUOTE_ID,IS_PRIMARY)
values(#{id},#{type.id}, #{birthdate, jdbcType=DATE}, default, #{enrollmentIndicator},
#{quoteId},#{isPrimary})
</insert>

Indeed the method returns the number of affected rows.
Sequence id is stored in ìd property of com.enrollment.vo.ConsumerVO passed as parameter.

Related

How can I access a value when inserting into a table?

I'm trying to write a java sql query, the simplified table would be table(name,version) with a unique constraint on (name, version).
I'm trying to insert a row into my database with a conditional statement. Meaning that when a entry with the same name exists, it should insert the row with same name and its version increased by 1.
I have tried with the following:
INSERT INTO table(name,version)
VALUES(?, CASE WHEN EXISTS(SELECT name from table where name=?)
THEN (SELECT MAX(version) FROM table WHERE name = ?) +1
ELSE 1 END)
values are sent by user.
My question is, how can I access the 'name' inside the values so I could compare them?
If you want to write this as a single query:
INSERT INTO table (name, version)
SELECT ?, COLAESCE(MAX(t2.version) + 1, 1)
FROM table t2
WHERE t2.name = ?;
That said, this is dangerous. Two threads could execute this query "at the same time" and possibly create the same version number. You can prevent this from happening by adding a unique index/constraint on (name, version).
With the unique index/constraint, one of the updates will fail if there is a conflict.
I see at least two approaches:
1. For each pair of name and version you first query the max version:
SELECT MAX(VERSION) as MAX FROM <table> WHERE NAME = <name>
And then you insert the result + 1 with a corresponding insert query:
INSERT INTO <table>(NAME,VERSION) VALUES (<name>,result+1)
This approach is very straight-forward, easy-to-read and implement, however, not really performant because of so many queries necessary.
You can achieve that with sql alone with sql analytics and window functions, e.g.:
SELECT NAME, ROW_NUMBER() over (partition BY NAME ORDER BY NAME) as VERSION FROM<table>
You can then save the result of this query as a table using CREATE TABLE as SELECT...
(The assumption here is that the first version is 1, if it is not the case, then one could slightly rework the query). This solution would be very performant even for large datasets.
You should get the name before insertion. In your case, if something went wrong then how would you know about it so you get the name before insert query.
Not sure but you try this:
declare int version;
if exists(SELECT name from table where name=?)
then
version = SELECT MAX(version) FROM table WHERE name = ?
version += 1
else
version = 1
end
Regards.
This is actually a bad plan, you might be changing what the user's specified data. That is likely to not be what is desired, maybe they're not trying to create a new version but just unaware that the one wanted already exists. But, you can create a function, which your java calls, not only inserts the requested version or max+1 if the requested version already exists. Moreover it returns the actual values inserted.
-- create table
create table nv( name text
, version integer
, constraint nv_uk unique (name, version)
);
-- function to create version or 1+max if requested exists
create or replace function new_version
( name_in text
, version_in integer
)
returns record
language plpgsql strict
as $$
declare
violated_constraint text;
return_name_version record;
begin
insert into nv(name,version)
values (name_in,version_in)
returning (name, version) into return_name_version;
return return_name_version;
exception
when unique_violation
then
GET STACKED DIAGNOSTICS violated_constraint = CONSTRAINT_NAME;
if violated_constraint like '%nv\_uk%'
then
insert into nv(name,version)
select name_in, 1+max(version)
from nv
where name = name_in
group by name_in
returning (name, version) into return_name_version;
return return_name_version;
end if;
end;
$$;
-- create some data
insert into nv(name,version)
select 'n1', gn
from generate_series( 1,3) gn ;
-- test insert existing
select new_version('n2',1);
select new_version('n1',1);
select *
from nv
order by name, version;

How to autogenerate two values when inserting at runtime?

I'm using MyBatis as DB framework in JAVA and I'm trying to generate automatically two values when inserting rows in a table: the task id and another value. This is my query:
<insert id="insertTwoValuesSequentialluy" parameterType="com.example.autogenerated.Task" >
<selectKey resultType="java.lang.String" keyProperty="taskId" order="BEFORE" >
select MY_TASK_ID_SEQUENCE.nextval from dual
</selectKey>
insert into DYDA_D.TASK_TABLE (taskId, otherVariable, autogeneratedValue)
values ( #{taskId,jdbcType=VARCHAR},
#{otherVariable,jdbcType=VARCHAR},
MY_SECOND_SEQUENCE.nextval = #{autogeneratedValue,jdbcType=VARCHAR})
</insert>
The code works fine but I'm having the following problem: while at runtime the Task instance gets its member taskId setted, it doesn't happen the same with autogeneratedValue, although when I check the database I can see the column matching autogeneratedValue isn't null for this new row. How can I get autogeneratedValue setted at runtime with no need of making a select query?
PS: don't pay attention to commas and the like, I have lots of columns and I've deleted most of them and changed names on the rest for this snippet. My point with the code is for you to see how I've generated the values, tags I've used etc.
You can do this like this:
<insert id="insertTwoValuesSequentialluy"
parameterType="com.example.autogenerated.Task"
useGeneratedKeys="true"
keyProperty="taskId,autogeneratedValue"
keyColumn="taksId,autogeneratedValue">
insert into DYDA_D.TASK_TABLE (taskId, otherVariable, autogeneratedValue)
values (MY_TASK_ID_SEQUENCE.nextval,
#{otherVariable,jdbcType=VARCHAR},
MY_SECOND_SEQUENCE.nextval)
</insert>

How can I get an id for a new record in a generic way? (JOOQ 3.4 with Postgres)

With jooq 3.4 I can't figure out how to do this (with Postgresql):
Query query = dsl.insertInto(TABLE)
.set(TABLE.ID, Sequences.TABLE_ID_SEQ.nextval());
but in a case when I don't know which is the exact table, something like this:
TableImpl<?> tableImpl;
Query query = dsl.insertInto(tableImpl)
.set(tableImpl.getIdentity(), tableImpl.getIdentity().getSequence().nextval());
Is it somehow possible?
I tried this:
dsl.insertInto(tableImpl)
.set(DSL.field("id"),
tableImpl.getSchema().getSequence("table_id_seq").nextval())
This works but I still don't know how to get the sequence name from the TableImpl object.
Is there a solution for this? Or is there a problem with my approach?
In plain SQL I would do this:
insert into table_A (id) VALUES nextval('table_A_id_seq');
insert into table_B (table_A_id, some_val) VALUES (currval('table_A_id_seq'), some_val);
So I need the value or a reference to that id for later use of the id that was generated for the inserted record as default, but I don't want to set any other values.
jOOQ currently doesn't have any means of associating a table with its implicitly used sequence for the identity column. The reason for this is that the sequence is generated when the table is created, but it isn't formally connected to that table.
Usually, you don't have to explicitly set the serial value of a column in a PostgreSQL database. It is generated automatically on insert. In terms of DDL, this means:
CREATE TABLE tablename (
colname SERIAL
);
is equivalent to specifying:
CREATE SEQUENCE tablename_colname_seq;
CREATE TABLE tablename (
colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;
The above is taken from:
http://www.postgresql.org/docs/9.3/static/datatype-numeric.html#DATATYPE-SERIAL
In other words, just leave out the ID values from the INSERT statements.
"Empty" INSERT statements
Note that if you want to create an "empty" INSERT statement, i.e. a statement where you pass no values at all, generating a new column with a generated ID, you can use the DEFAULT VALUES clause.
With SQL
INSERT INTO tablename DEFAULT VALUES
With jOOQ
DSL.using(configuration)
.insertInto(TABLENAME)
.defaultValues()
.execute();
Returning IDs
Note that PostgreSQL has native support for an INSERT .. RETURNING clause, which is also supported by jOOQ:
With SQL
INSERT INTO tablename (...) VALUES (...) RETURNING ID
With jOOQ
DSL.using(configuration)
.insertInto(TABLENAME, ...)
.values(...)
.returning(TABLENAME.ID)
.fetchOne();

mybatis - insert transaction

mybatis mapper code for insert:
<insert id="insert" parameterType="Shop" useGeneratedKeys="true">
insert into shop(email, pswd, nickname, mobile, city, create_date, status) values (#{email}, #{pswd}, #{nickname}, #{mobile}, #{city}, #{createDate}, #{status})
<selectKey keyProperty="id" order="AFTER" resultType="long">
select currval('shop_id_seq')
</selectKey>
</insert>
The database is postgresql 9.3.
My doubt is: without explicity transaction, when I retrieve the id from sequence with select currval('shop_id_seq'), is it possible to get the wrong value if other threads are also doing insert?
I thought it won't, because currval() function runs in context of current session, not global session, but I am not that sure.
According to PostgreSQL: Documentation: 9.3: Sequence Manipulation Functions, the sequence function currval:
Return the value most recently obtained by nextval for this sequence in the current session. (An error is reported if nextval has never been called for this sequence in this session.) Because this is returning a session-local value, it gives a predictable answer whether or not other sessions have executed nextval since the current session did.
So you will get the correct value. In another way, the sequences are non-transactional. Each session gets a distinct sequence value. The changes in the sequence can not be undone.

Get the id of last inserted record in mybatis

I am newbie to mybatis. I am trying to get the id of last inserted record. My database is mysql and my mapper xml is
<insert id="insertSelective" parameterType="com.mycom.myproject.db.mybatis.model.FileAttachment" >
<selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" >
SELECT LAST_INSERT_ID() as id
</selectKey>
insert into fileAttachment
<trim prefix="(" suffix=")" suffixOverrides="," >
<if test="name != null" >
name,
</if>
<if test="attachmentFileSize != null" >
size,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides="," >
<if test="name != null" >
#{name,jdbcType=VARCHAR},
</if>
<if test="attachmentFileSize != null" >
#{attachmentFileSize,jdbcType=INTEGER},
</if>
</trim>
</insert>
I thought statement written here 'SELECT LAST_INSERT_ID() as id' should return id of last inserted record but I am getting always 1 after inserting the record.
My mapper.java class I have method
int insertSelective(FileAttachment record);
In my dao class I am using
int id = fileAttachmentMapper.insertSelective(fileAttachment);
I am getting value of Id always 1 when new record is inserted. my Id field is auto incremented and records are inserting properly.
The id is injected in the object:
int num_of_record_inserted = fileAttachmentMapper.insertSelective(fileAttachment);
int id = fileAttachment.getId();
What selectKey does is to set the id in the object you are inserting, in this case in fileAttachment in its property id and AFTER record is inserted.
You only need to use
<insert id="insert" parameterType="com.mycom.myproject.db.mybatis.model.FileAttachment" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
There is no need of executing select query inside insert tag in MyBatis. It provides you new parameter for insert operation.Here define useGeneratedKeys="true", keyProperty="id", keyColumn="id".You can retrive value for id in following way
FileAttachment fileAttachment=fileAttachmentMapper.insertSelective(fileAttachment);
Integer id=fileAttachment.getId();
fileAttachment.getId() is used because in insert tag keyColumn="id" is define and you will get all return values inside fileAttachment reference of FileAttachment.
In fact, MyBatis has already provided this feature.You can use the option : "useGeneratedKeys" to get the last insert id.
Here is the explanation from MyBatis.(If you want to know more detailed info, you can go to MyBatis official page).
useGeneratedKeys (insert and update only) This tells MyBatis to use the JDBC getGeneratedKeys method to retrieve keys generated internally by the database (e.g. auto increment fields in RDBMS like MySQL or SQL Server). Default: false
If you are using xml:
<insert id="" parameterType="" useGeneratedKeys="true">
If you are using annotation:
#Insert("your sql goes here")
#Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
int insert(FileAttachment fileAttachment) throws Exception;
Once you finish insert operation, the fileAttachment's setId() method will be invoked, and is set to id of last inserted record. You can use fileAttachment's getId() to get the last insert id.
I do hope this will help you.
I think the 1 that is being returned refers to the number of records that is updated/inserted. I think the id is set on the fileAttachment parameter that you passed to the call to insertSelective.
I hope in the writer, you can have a custom composite writer and there you can get the inserted ids.
(1) Adding on to Ruju's answer, in the insert statement you need to define attribute useGeneratedKeys=true, parameterType="object", keyProperty="objectId" and keyColumn="objectId" should be set with same primary key column name (objectId) from the table that stores the object record. The primary key column (objectId) should be set to AUTO_INCREMENT in the database table.
(2) When the insert statement is triggered the new generated primary key (objectId) will be stored in object, and u can retrieve it by accessing objectId property through using this methods (object.getObjectId() or object.objectId). Now this should give the exact and new generated primary. It worked for me....
Configuration need with codeGenerator :
<table schema="catalogue" tableName="my_table" >
<generatedKey column="my_table_id" sqlStatement="JDBC" identity="true" />
<columnOverride column="my_table_id" isGeneratedAlways="true"/>
</table>
http://www.mybatis.org/generator/configreference/generatedKey.html
after code generation the insert include auto update for the id field
After struggling alot, I got the following solution:
Use Case: if you are using sequence for generating primary key. and you want the id inserted to db
in xml:
<insert id="createEmployee"
parameterType="com.test.EmployeeModel">
<selectKey keyProperty="employeeId" keyColumn="EMPLOYEE_ID"
resultType="Long" order="BEFORE">
select EMPLOYEE_SEQ.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO EMPLOYEE(EMPLOYEE_ID,EMPLOYEE_NAME)
VALUES
(#{employeeId,jdbcType=NUMERIC},#{EMPLOYEE_NAME,jdbcType=VARCHAR})
</insert>
in java side:
interface
public void createEmployee(EmployeeModel request);
in dao
getMapper().createEmployee(model);
getClient().commit();
Long employeeId= model.getEmployeeId();
System.out.println("Recent Employee Id: "+employeeId)

Categories