Save() Vs Saveandflush()

These 2 are basically the same, the only difference is when it comes when it's being applied in a @Transactional block.

This is because save() is marked as @Transactional. And by default, jpa will automatically flush after a @Transactional block. Therefore when calling save() on its own it's also flush()

When in a @Transactional block

Difference really comes when we have a @Transactional in our code for example

With save()

@Transactional 
public void myTransactionFunction() {
	Customer customer = new Customer();
	customer.setName("Austin")

	customerRepository.save(customer);
	String sqlQuery = "SELECT * FROM customer WHERE customer.id = ?";
	List<Customer> customers = jdbcTemplate.query(sqlQuery, new Object[]{customer.getId()}, new CustomerRowMapper());

	if (!customers.isEmpty()) {  
	  System.out.println("Customer found with id: " + customers.get(0).getId());  
	} else {  
	  System.out.println("Customer not found with id: " + customer.getId());  
	}

}

class CustomerRowMapper implements RowMapper<Customer> {  
  @Override  
  public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {  
    Customer customer = new Customer();  
    customer.setId(rs.getLong("id"));  
    customer.setName(rs.getString("name"));  
    // Map other columns as needed  
    return customer;  
  }  
}

In this example, we do a database query using select, the result would be Customer not found with id:. This is because since we annotated with @Transactional, the transaction now applied at the outer level outside of save() and hence, save() would not auto-flush anymore.

[!important]
In here if we call customerRepository.findById(customer.id) it would still return the valid customer. This is because customerRepository is cached and not represent the actual state of the database. However the select * from customer actually query the database and return the actual state of the database.

With saveAndFlush()

However the same example if we use saveAndFlush() we would be able to see the customer:

@Transactional 
public void myTransactionFunction() {
	Customer customer = new Customer();
	customer.setName("Austin")

	customerRepository.saveAndFlush(customer);
	String sqlQuery = "SELECT * FROM customer WHERE customer.id = ?";
	List<Customer> customers = jdbcTemplate.query(sqlQuery, new Object[]{customer.getId()}, new CustomerRowMapper());

	if (!customers.isEmpty()) {  
	  System.out.println("Customer found with id: " + customers.get(0).getId());  
	} else {  
	  System.out.println("Customer not found with id: " + customer.getId());  
	}

}

class CustomerRowMapper implements RowMapper<Customer> {  
  @Override  
  public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {  
    Customer customer = new Customer();  
    customer.setId(rs.getLong("id"));  
    customer.setName(rs.getString("name"));  
    // Map other columns as needed  
    return customer;  
  }  
}

As a result, we have Customer found with id: this is because saveAndFlush() actually write into the database. Since this is a @Transactional block, auto-flush has not been started in this block but and saveAndFlush() will flush it immediately.

[!important]
Since this changes has not been commited. Only this transactional when query the database can see the changes. When another thread query the database (or another application talk to the database in the same time), the changes are not visible.

When NOT in a @Transactional block

When not in a transactional block. Since by default, save() is transactional on its own and we have auto-flush from JPA after a transactional. save() is equivalent to saveAndFlush()

@Transactional 
public void myTransactionFunction() {
	Customer customer = new Customer();
	customer.setName("Austin")

	customerRepository.save(customer);
	String sqlQuery = "SELECT * FROM customer WHERE customer.id = ?";
	List<Customer> customers = jdbcTemplate.query(sqlQuery, new Object[]{customer.getId()}, new CustomerRowMapper());

	if (!customers.isEmpty()) {  
	  System.out.println("Customer found with id: " + customers.get(0).getId());  
	} else {  
	  System.out.println("Customer not found with id: " + customer.getId());  
	}

}

class CustomerRowMapper implements RowMapper<Customer> {  
  @Override  
  public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {  
    Customer customer = new Customer();  
    customer.setId(rs.getLong("id"));  
    customer.setName(rs.getString("name"));  
    // Map other columns as needed  
    return customer;  
  }  
}

Will also return Customer found with id: here.