I'm trying to write an equivalent of Rails data model evolution/rollback mechanism using Spring Jdbc.
Spring Jdbc transactionnal insert/replace works very well (DataSourceTransactionManager with PROPAGATION_REQUIRED under InnoDB mysql 5) :
// Transaction begins
getJdbcTemplate().execute("replace into aTable ...");
getJdbcTemplate().execute("wrong request");
getJdbcTemplate().execute("replace into aTable ...");
// none are commited
but alter doesn't :
// Transaction begins
getJdbcTemplate().execute("alter table aTable add column `columnForTest` ...");
getJdbcTemplate().execute("wrong request");
getJdbcTemplate().execute("alter table aTable add column `columnForTest` ...");
// the first alter is commited
Is there a way to achieve atomicity (all-or-none behavior) with alter ?
Thanks in advance
ALTER TABLE (and other DDL operations) are usually non-transactional, depending on the database. Spring and JDBC have no control over this. If a non-transactional operation is performed inside a transaction, it will be performed non-transactionally.
So it comes down to the database, and how it is configured, rather than being an issue with the client.
Related
Is there a way to create partitions from Java spring code, in case any database is missing partition?
In the DB we write query like this -
ALTER TABLE <table_name> ADD PARTITION (PARTITION p1 VALUES LESS THAN (time));
You can fire any random SQL query in spring application using jdbcTemplate object.
jdbcTemplate.execute("DROP TABLE customers IF EXISTS");
I am new to databases and transactions. I find a lot of different information up-to-date and am currently trying to organize my thoughts.
Regarding the context, I'm trying to test the current isolation level with the SQL Server, but I don't succeed.
For this I use Spring Transaction together with Eclipse Link. I found some information about Spring transactions (https://www.marcobehler.com/guides/spring-transaction-management-transactional-in-depth). However, there is still a concept of the works unit and now I don't know what is currently being used (https://wiki.eclipse.org/Introduction_to_EclipseLink_Transactions_(ELUG)#Unit_of_Work_Architecture)
What I am trying to test:
I have an Entity User (id, firstname, lastname). I have an entry in table id = 1, firstname = foo lastname = bar
I have a service and transaction. IMHO default is isolation READ_COMMIT for SQL Server
#Transactional
public User updateUser(){
User updateUser = new User(1, "new firstname", "new lastname");
User updatedUser = userRepository.save(updateUser); --> em.merge
return updatedUser;
}
So far so good. What I do not understand now. I set a breakpoint in the return.
At the same time I opened a second SQL client and executed the following SQL.
SET IMPLICIT_TRANSACTIONS ON
UPDATE
User
SET
lastname = 'complete new',
WHERE
id = 1
COMMIT TRAN
What I would expect is that the SQL statement will wait for the Spring transaction to complete. BUT this is currently not the case, the SQL statement is simply carried out.
Then lastname is in the table lastname "complete new" then I resume the breakpoint and then the lastname is "new lastname". This behavior I cannot understand. Is this normal or is this because of the unit work of eclipse link?
EclipseLink buffers SQL statements that need to be executed for as long as possible in order to reduce the lock time in the RDBMS. In your particular case the JDBC driver will receive the UPDATE statement when Spring Data JPA commits the transaction. You can verify it by enabling SQL logging in EclispeLink: stackoverflow.com/q/2374395/17695211
After enabling SQL logging you'll see that there won't be any SQL debug output in the console at your breakpoint. It will appear after the return. If you really want to see the locking effect, you need to write the repository without Spring Data JPA with a #PersistenceContext-injected EntityManager and call EntityManager.flush which will flush SQL statement buffer of EclipseLink to the JDBC driver before the breakpoint.
Alternatively (and if you feel adventurous), you may try looking for a place in the EclipseLink source code where it executes the corresponding PreparedStatement, and set the breakpoint right after it.
I think this question is similar to Data base pessimistic locks with Spring data JPA (Hibernate under the hood) but thought I would ask separately as not exactly the same.
I have a multi threaded/node springboot application on top of a mariadb database with a table like
CREATE TABLE job (
id INT PRIMARY KEY AUTO_INCREMENT,
owner VARCHAR(50),
status VARCHAR(10) );
Have a Job domain class as you'd expect.
Have a JobRepository interface which extends CrudRepository<Job,Integer> and a service class.
The application rule is we cannot insert a new job if same owner and set of status values. e.g. If this was old school native sql I would just:
START TRANSACTION;
INSERT INTO job (owner, status)
SELECT 'fred', 'init' FROM DUAL
WHERE NOT EXISTS
( SELECT 1 FROM job
WHERE owner = 'fred' AND status IN ('init', 'running')
);
COMMIT;
But how to I do this in JPA/CrudRepository.
I split into DB operations. Defined a repository method:
#Lock(LockModeType.READ)
long countByOwnerAndStatusIn(String owner, List<String> status);
And then had a service method like:
#Transactional
public Job createJob(Job job) {
if (jobRepository.countByOwnerAndStatusIn(job.getOwner(), Job.PROGRESS_STATUS) == 0) {
// Sleeps just to ensure conflicts
Thread.sleep(1000);
Job newJob = jobRepository.save(job);
Thread.sleep(1000);
return newJob
}
else {
return null;
}
}
But with this I do not get the desired effect.
LockModeType of READ and WRITE allow duplicates to be created.
LockModeType of PESSIMISTIC_READ and PESSIMISTIC_WRITE can result in deadlock errors.
So I guess I am after one of two options:
Is there a way to make get the INSERT...SELECT WHERE NOT EXISTS
into a JPA/CrudRepository method?
Is there a way to get the serivce
method to effectively wrap the count check and the insert in the same
lock?
If there is no way to do either I guess I'll try and get access to the underlying jdbc connection and explicity run a LOCK TABLE statement (or the insert...select, but I don't like the idea of that, keeping it JPA like is probably better).
Hope I have explained myself properly. Thanks in advance for your help.
I'm trying to set a Lock for the row I'm working on until the next commit:
entityManager.createQuery("SELECT value from Table where id=:id")
.setParameter("id", "123")
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 10000)
.getSingleResult();
What I thought should happen is that if two threads will try to write to the db at the same time, one thread will reach the update operation before the other, the second thread should wait 10 seconds and then throw PessimisticLockException.
But instead the thread hangs until the other thread finishes, regardless of the timeout set.
Look at this example :
database.createTransaction(transaction -> {
// Execute the first request to the db, and lock the table
requestAndLock(transaction);
// open another transaction, and execute the second request in
// a different transaction
database.createTransaction(secondTransaction -> {
requestAndLock(secondTransaction);
});
transaction.commit();
});
I expected that in the second request the transaction will wait until the timeout set and then throw the PessimisticLockException, but instead it deadlocks forever.
Hibernate generates my request to the db this way :
SELECT value from Table where id=123 FOR UPDATE
In this answer I saw that Postgres allows only SELECT FOR UPDATE NO WAIT that sets the timeout to 0, but it isn't possible to set a timeout in that way.
Is there any other way that I can use with Hibernate / JPA?
Maybe this way is somehow recommended?
Hibernate supports a bunch of query hints. The one you're using sets the timeout for the query, not for the pessimistic lock. The query and the lock are independent of each other, and you need to use the hint shown below.
But before you do that, please be aware, that Hibernate doesn't handle the timeout itself. It only sends it to the database and it depends on the database, if and how it applies it.
To set a timeout for the pessimistic lock, you need to use the javax.persistence.lock.timeout hint instead. Here's an example:
entityManager.createQuery("SELECT value from Table where id=:id")
.setParameter("id", "123")
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 10000)
.getSingleResult();
I think you could try
SET LOCAL lock_timeout = '10s';
SELECT ....;
I doubt Hibernate supports this out-of-box. You could try find a way to extend it, not sure if it worth it. Because I guess using locks on a postges database (which is mvcc) is not the smartest option.
You could also do NO WAIT and delay-retry several times from your code.
There is the lock_timeout parameter that does exactly what you want.
You can set it in postgresql.conf or with ALTER ROLE or ALTER DATABASE per user or per database.
The hint for lock timeout for PostgresSQL doesn't work on PostreSQL 9.6 (.setHint("javax.persistence.lock.timeout", 10000)
The only solution I found is uncommenting lock_timeout property in postgresql.conf:
lock_timeout = 10000 # in milliseconds, 0 is disabled
For anyone who's still looking for a data jpa solution, this is how i managed to do it
First i've created a function in postgres
CREATE function function_name (some_var bigint)
RETURNS TABLE (id BIGINT, counter bigint, organisation_id bigint) -- here you list all the columns you want to be returned in the select statement
LANGUAGE plpgsql
AS
$$
BEGIN
SET LOCAL lock_timeout = '5s';
return query SELECT * from some_table where some_table.id = some_var FOR UPDATE;
END;
$$;
then in the repository interface i've created a native query that calls the function. This will apply the lock timeout on that particular transaction
#Transactional
#Query(value = """
select * from function_name(:id);
""", nativeQuery = true)
Optional<SomeTableEntity> findById(Long id);
I am developing a web application with java 2 ee. I also use hibernate and mysql.
in order to restore the backup file, in some point in my application i need to drop the current database and recreate it, i do it as follow :
Connection conn = (Connection) DriverManager.getConnection("jdbc:mysql://localhost/?user=user&password=pass");
Statement statement = (Statement) conn.createStatement();
statement.executeUpdate("DROP DATABASE tcs;");
statement.executeUpdate("CREATE DATABASE tcs charset=utf8 collate=utf8_persian_ci;");
after dropping and recreating i need to initialize database with default user (spring security user)
User admin = new User();
UserAuthority ROLE_USER = new UserAuthority("ROLE_USER");
ROLE_USER.save();
admin.addUserAuthority(ROLE_USER);
admin.setEnabled(true);
admin.save();
but at the last line application throws this exception
Hibernate: insert into roles (authority) values (?)
[DEBUG][16:19:17,734] [http-bio-8080-exec-10] NewPooledConnection:367 com.mchange.v2.c3p0.impl.NewPooledConnection#bcbe33a handling a throwable.
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'tcs.roles' doesn't exist
i know hibernate creates tables at startup but in this case it fails to recreate the tables after a drop/recreate , so how can i force hibernate to create tables again?
or hypothetically is there something like Hibernate.createTable(Class) ?
You need to add the following property to your configuration:
Drop and Recreate everytime SessionFactory is created and destroyed:
<property name="hbm2ddl.auto">create-drop</property>
Other possible options:
validate: validate the schema, makes no changes to the database.
update: update the schema.
create: creates the schema, destroying previous data.
for future googlers :
it was so simple after all , i just needed to build session factory so i added this line and it worked like charm :
new Configuration().configure().buildSessionFactory();
note that buildSessionFactory() is deprecated in hibernate 4 but i guess you can use the code here to do the same.