I'm using Spring MVC with Hibernate but I've reached a weird situation, when my Service annotated with #Transational finishes it's execution it seems that hibernate is executing an extra update in my entity, an update that is not coded anywhere. Could someone help me?
My Controller:
#Controller
public class UserController {
#Resource(name="userService")
private UserService userService;
#RequestMapping(value="/user/create", method=RequestMethod.GET)
public ModelAndView createUser(User user) throws GenericException {
this.userService.doTestOnUpdateUser();
return new ModelAndView("createUser");
}
My Service:
#Service("userService")
public class UserServiceImpl implements UserService {
#Resource(name = "userDao")
private UserDao userDao;
#Override
#Transactional(rollbackFor = CauseTrasactionRollback.class)
public void doTestOnUpdateUser() {
try {
User user = this.userDao.findById(40l);
user.setFirstName("Rogerio1234");
user.setLastName("R123");
user.setEmail("rro#gmail.com");
user.setPassword("novo password");
this.userDao.save(user);
System.out.println("why ?");
} catch (DaoException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
My DAO:
#Repository("userDao")
public class UserDaoImpl extends CrudDaoImpl<User> implements UserDao {
#Autowired
public UserDaoImpl(SessionFactory sessionFactory) {
super(sessionFactory);
}
#Override
public void save(User user) throws DaoException {
try {
String sql = "";
User userBefore = null;
if (user.getId() == null) {
user.setCreated(new Date());
sql = "insert into users (first_name, last_name, email, password, created, last_updated, email_confirmed_at)"
+ "values (:firstName, :lastName, :email, md5(:password), :created, :lastUpdated, :emailConfirmedAt)";
} else {
userBefore = this.findById(user.getId());
user.setLastUpdated(new Date());
if (userBefore.getPassword() != user.getPassword()) {
sql = "update users set first_name=:firstName, last_name=:lastName, email=:email, "
+ " password=md5(:password), created=:created, last_updated=:lastUpdated, "
+ " email_confirmed_at=:emailConfirmedAt where id=:id";
} else {
sql = "update users set first_name=:firstName, last_name=:lastName, email=:email, "
+ "created=:created, last_updated=:lastUpdated, email_confirmed_at=:emailConfirmedAt where id=:id";
}
}
SQLQuery sqlQuery = this.sessionFactory.getCurrentSession()
.createSQLQuery(sql);
if (user.getId() != null) {
sqlQuery.setLong("id", user.getId());
if (userBefore.getPassword() != user.getPassword()) {
sqlQuery.setString("password", user.getPassword());
}
} else {
sqlQuery.setString("password", user.getPassword());
}
sqlQuery.setString("firstName", user.getFirstName());
sqlQuery.setString("lastName", user.getLastName());
sqlQuery.setString("email", user.getEmail());
sqlQuery.setDate("created", user.getCreated());
sqlQuery.setDate("lastUpdated", user.getLastUpdated());
sqlQuery.setDate("emailConfirmedAt", user.getEmailConfirmedAt());
sqlQuery.executeUpdate();
} catch (Exception e) {
throw new DaoException("Error at saving "
+ user.getClass().getSimpleName(), e);
}
}
}
Console log shows this:
Hibernate: select user0_.id as id8_, user0_.created as created8_, user0_.last_updated as last3_8_, user0_.email as email8_, user0_.email_confirmed_at as email5_8_, user0_.first_name as first6_8_, user0_.last_name as last7_8_, user0_.password as password8_ from users user0_ where user0_.id=?
Hibernate: update users set created=?, last_updated=?, email=?, email_confirmed_at=?, first_name=?, last_name=?, password=? where id=?
Hibernate: select user0_.id as id8_, user0_.created as created8_, user0_.last_updated as last3_8_, user0_.email as email8_, user0_.email_confirmed_at as email5_8_, user0_.first_name as first6_8_, user0_.last_name as last7_8_, user0_.password as password8_ from users user0_ where user0_.id=?
Hibernate: update users set first_name=?, last_name=?, email=?, created=?, last_updated=?, email_confirmed_at=? where id=?
why ?
Hibernate: update users set created=?, last_updated=?, email=?, email_confirmed_at=?, first_name=?, last_name=?, password=? where id=?
I'm not understanding why is executing this two updates that are not coded anywhere. The problem is that I'm using MD5 on postgres to encrypt the user's password, and I cannot let it update the password every time some other attribute gets updated, or else the password will be encrypted again, changing it, but right now those two updates not coded are setting the password in database without encrypting it to "novo password".
Any help will be appreciated.
Related
I want to test how JDBC transactions work. Particularly, I want to see a read of uncommitted data. I've written one integration test in spring boot environment using a locally installed PostgreSQL database.
I'm trying to insert a row into a table, read it from one transaction, then update from another transaction without committing it, and read it again hoping it would change.
Table for the test (DDL):
create table users
(
id integer default nextval('user_id_sequence'::regclass) not null
constraint users_pkey
primary key,
first_name varchar(255) not null,
second_name varchar(255) not null,
email varchar(255)
);
alter table users
owner to postgres;
The test:
public void testHealthCheck() throws SQLException {
Connection zeroConnection = dataSource.getConnection();
Integer insertedUserId = insertUserSilently(zeroConnection, new User()
.setFirstName("John")
.setSecondName("Doe")
.setEmail("johndoe#gmail.com"));
zeroConnection.close();
Connection firstConnection = dataSource.getConnection();
firstConnection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
firstConnection.setAutoCommit(false);
Connection secondConnection = dataSource.getConnection();
secondConnection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
secondConnection.setAutoCommit(false);
List<User> users = getAllUsersSilently(firstConnection);
log.info("Got users: {}", silentToJsonString(users));
PersistenceUtils.updateUserEmailSilently(secondConnection, insertedUserId, "johndoe#yahoo.com");
users = getAllUsersSilently(firstConnection);
log.info("Got users: {}", silentToJsonString(users));
secondConnection.rollback();
secondConnection.close();
users = getAllUsersSilently(firstConnection);
log.info("Got users: {}", silentToJsonString(users));
firstConnection.close();
}
Utility class:
private static final String INSERT_USER_SQL = "insert into users(first_name, second_name, email) values (?, ?, ?)";
private static final String UPDATE_USER_SQL = "update users set email = ? where id = ?;";
private static final String SELECT_ALL_USERS_SQL = "select * from users";
public static List<User> extractUsersSilently(ResultSet resultSet) {
List<User> resultList = newArrayList();
try {
while (resultSet.next()) {
Integer id = resultSet.getInt(1);
String firstName = resultSet.getString(2);
String secondName = resultSet.getString(3);
String email = resultSet.getString(4);
resultList.add(new User(id, firstName, secondName, email));
}
} catch (SQLException e) {
log.error("Error while extracting result set", e);
return emptyList();
}
return resultList;
}
public static Integer insertUserSilently(Connection connection, User user) {
try {
PreparedStatement insertStatement = connection.prepareStatement(INSERT_USER_SQL, Statement.RETURN_GENERATED_KEYS);
insertStatement.setString(1, user.getFirstName());
insertStatement.setString(2, user.getSecondName());
insertStatement.setString(3, user.getEmail());
insertStatement.execute();
ResultSet resultSet = insertStatement.getGeneratedKeys();
resultSet.next();
return resultSet.getInt(1);
} catch (Exception exception) {
log.error(format("Exception while inserting user %s", user), exception);
return -1;
}
}
public static List<User> getAllUsersSilently(Connection connection) {
try {
PreparedStatement selectStatement = connection.prepareStatement(SELECT_ALL_USERS_SQL);
selectStatement.execute();
return extractUsersSilently(selectStatement.getResultSet());
} catch (Exception exception) {
log.error("Exception while getting all users", exception);
return Collections.emptyList();
}
}
public static void updateUserEmailSilently(Connection connection, Integer userId, String userEmail) {
try {
PreparedStatement updateStatement = connection.prepareStatement(UPDATE_USER_SQL);
updateStatement.setString(1, userEmail);
updateStatement.setInt(2, userId);
updateStatement.execute();
} catch (Exception exception) {
log.error(format("Exception while updating user %d", userId), exception);
}
}
}
Actual results are (you have to clear table manually before the test):
Got users:
[{"id":55,"firstName":"John","secondName":"Doe","email":"johndoe#gmail.com"}]
Got users:
[{"id":55,"firstName":"John","secondName":"Doe","email":"johndoe#gmail.com"}]
Got users:
[{"id":55,"firstName":"John","secondName":"Doe","email":"johndoe#gmail.com"}]
Although second read should've seen uncommitted change to email.
Cannot read uncommitted data in Postgres
See section 13.2. Transaction Isolation of the PostgreSQL documentation:
In PostgreSQL, you can request any of the four standard transaction isolation levels, but internally only three distinct isolation levels are implemented, i.e. PostgreSQL's Read Uncommitted mode behaves like Read Committed. This is because it is the only sensible way to map the standard isolation levels to PostgreSQL's multiversion concurrency control architecture.
This means that if you want to test TRANSACTION_READ_UNCOMMITTED, you need a DBMS other than PostgreSQL.
I have a DAO User with Hibernate framework.
I am doing some Junit test about it. Testing create/update/delete on user, works for create but not for update and delete but my test passed.
I need to verify what is wrong with my update, delete functions.
I tried to add a session.save() befose the update NOK
I tried to replace session.update() to session.saveOrUpdate() NOK
I tried to use the user parameter instead of create a new user NOK
Junit function update/delete only displaying update and delete
#Test
public void testCreateUpdateDeleteUser(){
User userCRUD = new User("morganeflamant#gmail.com", "admin", new
Date(System.currentTimeMillis()), new Date(System.currentTimeMillis()));
userCRUD = serviceFacade.getUserDao().findUserById(userCRUD.getId_user());
Assert.assertNotNull(userCRUD);
userCRUD.setId_user(userCRUD.getId_user());
userCRUD.setEmail("testmorgane#gmail.com");
userCRUD.setRole("member");
userCRUD = serviceFacade.getUserDao().updateUser(userCRUD);
Assert.assertNotNull(userCRUD);
Assert.assertNotNull(userCRUD.getUpdateAt());
userCRUD = serviceFacade.getUserDao().findUserById(userCRUD.getId_user());
Assert.assertTrue(serviceFacade.getUserDao().deleteUser(userCRUD));
}
Userdao update and delete function:
#Override
public User updateUser(User user) {
Session session = SingletonHibernate.getSessionFactory().openSession();
session.beginTransaction();
User u = new User(user.getId_user(), user.getEmail(), user.getRole(), user.getCreateAt(), user.getUpdateAt());
u.setId_user(user.getId_user());
u.setEmail(user.getEmail());
u.setRole(user.getRole());
u.setCreateAt(user.getCreateAt());
u.setUpdateAt(user.getUpdateAt());
session.saveOrUpdate(u);
session.close();
return u;
}
#Override
public boolean deleteUser(User user) {
Session session = SingletonHibernate.getSessionFactory().openSession();
session.beginTransaction();
User u = (User) session.get(User.class, user.getId_user());
if(u != null){
session.delete(u);
return true;
}
return false;
}
In my db I actually have :
60
morganeflamant#gmail.com
admin
2019-07-19 13:38:19
2019-07-19 13:38:19
but I am supposed to have this:
60
testmorgane#gmail.com
admin
2019-07-19 13:38:19
2019-07-19 13:38:19
for the update part and the delete part I am not supposed to have the user 60.
UPDATE:
adding this line :
session.getTransaction().commit();
after the session.save() and the changes are done.
Thank you for your comments !
I have 200K rows to be inserted in one single database table. I tried to use jdbcTemplate.batchUpdate in spring in order to do insertion 10,000 per batch. However, this process consumes too much time (7 mins for 200K rows). So on database side, I check the number of rows inserted by select count(*) from table_X. I found the number of rows increased slightly instead of 10K expected. Can anyone explain what's reason or is it something which should be configurated on Database side ?
PS: I am using sybase ....
There are lot of approaches available on the web.
Performance directly depends on the
Code you have written
JDBC driver you are using
database server and number of connection you are using
table indexes leads to slowness for insertion
Without looking at your code anyone can guess, but no one can find the exact solution.
Approach 1
//insert batch example
public void insertBatch(final List<Customer> customers){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
getJdbcTemplate().batchUpdate(sql, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Customer customer = customers.get(i);
ps.setLong(1, customer.getCustId());
ps.setString(2, customer.getName());
ps.setInt(3, customer.getAge() );
}
#Override
public int getBatchSize() {
return customers.size();
}
});
}
reference
https://www.mkyong.com/spring/spring-jdbctemplate-batchupdate-example/
http://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch12s04.html
Approach 2.1
//insert batch example
public void insertBatch(final List<Customer> customers){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
List<Object[]> parameters = new ArrayList<Object[]>();
for (Customer cust : customers) {
parameters.add(new Object[] {cust.getCustId(),
cust.getName(), cust.getAge()}
);
}
getSimpleJdbcTemplate().batchUpdate(sql, parameters);
}
Alternatively, you can execute the SQL directly.
//insert batch example with SQL
public void insertBatchSQL(final String sql){
getJdbcTemplate().batchUpdate(new String[]{sql});
}
reference
https://www.mkyong.com/spring/spring-simplejdbctemplate-batchupdate-example/
Approach 2.2
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}
// ... additional methods
}
Approach 2.3
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(),
actor.getLastName(),
actor.getId()};
batch.add(values);
}
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
return updateCounts;
}
// ... additional methods
}
Approach 3 :JDBC
dbConnection.setAutoCommit(false);//commit trasaction manually
String insertTableSQL = "INSERT INTO DBUSER"
+ "(USER_ID, USERNAME, CREATED_BY, CREATED_DATE) VALUES"
+ "(?,?,?,?)";
PreparedStatement = dbConnection.prepareStatement(insertTableSQL);
preparedStatement.setInt(1, 101);
preparedStatement.setString(2, "mkyong101");
preparedStatement.setString(3, "system");
preparedStatement.setTimestamp(4, getCurrentTimeStamp());
preparedStatement.addBatch();
preparedStatement.setInt(1, 102);
preparedStatement.setString(2, "mkyong102");
preparedStatement.setString(3, "system");
preparedStatement.setTimestamp(4, getCurrentTimeStamp());
preparedStatement.addBatch();
preparedStatement.executeBatch();
dbConnection.commit();
reference
https://www.mkyong.com/jdbc/jdbc-preparedstatement-example-batch-update/
/*Happy Coding*/
Try setting below for connection string - useServerPrepStmts=false&rewriteBatchedStatements=true. Have not tried but its from my bookmarks. You can search on these lines..
Connection c = DriverManager.getConnection("jdbc:<db>://host:<port>/db?useServerPrepStmts=false&rewriteBatchedStatements=true", "username", "password");
For us moving the code to a wrapper class and annotating the batch insert method with #Transactional did solve the problem.
I have this #NamedNativeQuery
#NamedNativeQuery(name = "Certificacion.updateCertificacionAclaraciones", query = "UPDATE CERTIFICACION "
+ "SET PENDIENTE_GENERACION = :pendienteGeneracion, ID_ACLARACIONES_TEMP_ESCRITO = :idAclaracion "
+ "WHERE ID IN (:paso)")
And I have this EJB structure to get a transaction
#TransactionManagement(TransactionManagementType.CONTAINER)
#Stateless
public class PRequerimientoCCServiceBean implements
IPRequerimientoCCServiceBeanLocal {
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void guardarRequerimiento(Usuario usuario,
ConjuntoCertificaciones conjunto, String aclaracionGeneral,
Map<Long, String> mapAclaracionColegio) throws RollbackException {
try {
// Realizamos el guardado de las aclaraciones y la generaciĆ³n del
// documento
AclaracionesTempEscrito currentAclaracion = new AclaracionesTempEscrito();
...
entityManager.persist(currentAclaracion);
generarDocumento(currentAclaracion, conjunto, usuario);
} catch (Exception e) {
ejbContext.setRollbackOnly();
throw new RollbackSajgException();
}
}
#TransactionAttribute(TransactionAttributeType.MANDATORY)
private void generarDocumento(AclaracionesTempEscrito findAclaracion,
ConjuntoCertificaciones conjunto, Usuario usuario)
throws RollbackException {
...
Query actualizaCertificacionesAclaracion = entityManager
.createNamedQuery("Certificacion.updateCertificacionAclaraciones");
actualizaCertificacionesAclaracion
.setParameter("pendienteGeneracion", true)
.setParameter("idAclaracion", findAclaracion.getId())
.setParameter("paso", paso).executeUpdate();
}
}
It suppose that I will execute commit when guardarRequerimiento method ends but when I execute my nativeQuery with executeUpdate I get an exception of ConstraintViolation
org.hibernate.exception.ConstraintViolationException: could not
execute statement
I think that it is because although findAclaracion.getId() exists in the transaction, it does not exist in database and the executeUpdate need that this object exist in database because is not in my persistence context when execute.
Why do I get this behaviour? How can I solve?
Thank you.
You can try to refresh this object after flush and before generarDocumento(currentAclaracion, conjunto, usuario);
or
you can do it as query and loop that objectList updating values..
Newbie programmer here. Upon doing mvn tomcat:run I get the following error:
SEVERE: Servlet.service() for servlet appServlet threw exception
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'values (?, ?)' at line 1
The code in question is as follows:
public void create(User user) {
this.jdbcTemplate.update("INSERT INTO xyz.user(user_name, user_password values (?, ?)");
user.getUserName(); user.getId();
}
public void delete(User user) {
this.jdbcTemplate.update("DELETE FROM xyz.user WHERE id = ?");
}
public void update(User user) {
this.jdbcTemplate.update(
"UPDATE xyz.user SET UserName = ? password = ? WHERE id = ?");
Googled - couldn't find a solution for (?, ?) scenarios. Pls. help - Thx in advance :)
Here's the complete code (almost) - I am doing something wrong but can't figure out what.
public User find(String login) {
System.out.println("Trying to find the user...." + login);
User user = this.jdbcTemplate.queryForObject(
"select * from xyz where user_name = ?",
new Object[]{login},
new RowMapper<User>() {
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(Long.valueOf(rs.getInt(1)));
user.setUserName(rs.getString(2));
user.setPassword(rs.getString(3));
return user;
}
});
System.out.println("Found user..." + user);
return user;
}
public void create(User user) {
this.jdbcTemplate.update("INSERT INTO ibstechc_dev.user(user_name, user_password) VALUES (?,?)");
user.getUserName(); user.getId() ;
}
public void delete(User user) {
this.jdbcTemplate.update("DELETE FROM xyz WHERE id = ?");
// TODO Auto-generated method stub
}
public void update(User user) {
this.jdbcTemplate.update(
"UPDATE xyz SET user_name = ?, user_password = ? WHERE id = ?");
// TODO Auto-generated method stub
}
}
I am stuck with the same error - tomcat:run throws the following -
SEVERE: Servlet.service() for servlet appServlet threw exception
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?,?)' at line 1
Use this code:
this.jdbcTemplate.update("INSERT INTO xyz.user(user_name, user_password) values (?, ?)");
There was an issue with your SQL statement. To give you perspective you were trying to do:
INSERT INTO xyz.user(user_name, user_password values ('testuser','testpass'))
instead of
INSERT INTO xyz.user(user_name, user_password) values ('testuser','testpass'))
Hope this makes sense?
I think here is the sql syntax problem:
INSERT INTO xyz.user(user_name, user_password values (?, ?)
replace this by
INSERT INTO xyz.user(user_name, user_password) values (?, ?);
There is a sql syntax error in update
public void update(User user) {
this.jdbcTemplate.update(
"UPDATE xyz.user SET UserName = ? password = ? WHERE id = ?");
this is not the way to update,give a comma(,) like this
public void update(User user) {
this.jdbcTemplate.update(
"UPDATE xyz.user SET UserName = ? ,password = ? WHERE id = ?");
your error is this statement
this.jdbcTemplate.update("INSERT INTO xyz.user(user_name, user_password values (?, ?)");
It should be
this.jdbcTemplate.update("INSERT INTO xyz.user(user_name, user_password) values (?, ?)");
Hope it helps
Cheers
Try this:-
this.jdbcTemplate.update("INSERT INTO xyz.user(user_name, user_password) values (?, ?)");
instead of
this.jdbcTemplate.update("INSERT INTO xyz.user(user_name, user_password values (?, ?)");