I want to log changes of an account. Therefore, I have created an entity class that shall log the changes.
Each time, an account entity is saved or updated a logging object is created.
When the object is updated with a new balance the old balance shall be retrieved from the database.
As the object is bound to the session retrieving its old balance is not trivial, because one always gets the new balance.
To circumvent, I detached the object from the session. Yet, this seems to be a workaround that should be avoided.
The following code snippets shall illustrate the scenario.
Any suggestion is highly appreciated!
The test:
public class AccountServiceTest
{
#Autowired
AccountService accountService;
#Autowired
ChangeAccountService changeAccountService;
#Test
public void shouldHaveChangeLog()
{
Account account = this.accountService.updateAccount(new Account(0, 10.0));
assertThat(account.getId(), is(not(0L)));
account.setBalance(20.0);
account = this.accountService.updateAccount(account);
final List<ChangeAccountLog> changeResultLogs = this.changeAccountService.findAll();
assertThat(changeResultLogs.get(1).getNewBalance(), is(not(changeResultLogs.get(1).getOldBalance())));
}
}
The service of the domain class to be logged:
#Service
public class AccountService
{
#Autowired
AccountRepository accountRepository;
#Autowired
ChangeAccountService changeAccountService;
public Account findById(final long id)
{
return this.accountRepository.findOne(id);
}
public Account updateAccount(final Account account)
{
this.changeAccountService.saveLog(account);
return this.accountRepository.save(account);
}
}
The service of the logging class:
#Service
public class ChangeAccountService
{
#Autowired
AccountService accountService;
#Autowired
ChangeAccountLogRepository repository;
public ChangeAccountLog save(final ChangeAccountLog changeAccountLog)
{
return this.repository.save(changeAccountLog);
}
public List<ChangeAccountLog> findAll()
{
return this.repository.findAll();
}
public ChangeAccountLog saveLog(final Account account)
{
final Double oldAccountBalance = oldAccountBalance(account);
final Double newAccountBalance = account.getBalance();
final ChangeAccountLog changeAccountLog = new ChangeAccountLog(0, oldAccountBalance, newAccountBalance);
return this.repository.save(changeAccountLog);
}
#PersistenceContext
EntityManager em;
private Double oldAccountBalance(final Account account)
{
this.em.detach(account);
final Account existingAccount = this.accountService.findById(account.getId());
if (existingAccount != null)
{
return existingAccount.getBalance();
}
return null;
}
}
The class of which objects are to be logged:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Account
{
#Id
#GeneratedBalance
protected long id;
Double balance;
}
The logging class:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class ChangeAccountLog
{
#Id
#GeneratedBalance
private long id;
private Double oldBalance;
private Double newBalance;
}
You might want to use Hibernate Envers to create a versioning table instead of creating separate log objects.
Related
Im trying to build a simple web service, right now I only have 3 classes, an entity class, a DAO class and a tester class.
My entity class:
#Entity
#Table(name="sales")
public class Sale implements Serializable{
#Id
#Column(name = "idsale_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idsale_id;
#Column(name = "grand_total", nullable = false)
private double grand_total;
public Sale() {
}
public Sale(double grand_total) {
this.grand_total = grand_total;
}
My Database Operations class
#ApplicationScoped
public class DatabaseOperations {
#PersistenceContext(unitName = "owlJPA")
EntityManager em;
#Transactional
public String createSale(double grand_total) {
Sale sale = new Sale(grand_total);
em.persist(sale);
em.flush();
return "Successfully added new entry in DB";
}
}
REST handling code
#RequestScoped
#Path("/hello-world")
public class HelloResource {
#Inject
DatabaseOperations databaseOperations;
#POST
#Produces("text/plain")
#Consumes(MediaType.APPLICATION_JSON)
public String POSTrecieved(JsonObject jsonRaw) {
DatabaseOperations databaseOperations = new DatabaseOperations();
try {
String tempStr = jsonRaw.getJsonObject("newSale").getString("grand_total");
double grand_total = Double.parseDouble(tempStr);
String x = databaseOperations.createSale(grand_total);
return "SUCESSFULLY ADDED NEW SALE, with grand total of: "+x;
}
catch(Exception error){
return error.toString();
}
}
Whenever I try and run a transaction by calling the createSale method, the sale object gets created just fine, but i get a nullPointerException error as my entityManager em is null. But shouldn't my entityManager em already be instantialized as i did #ApplicationScoped?
I'm using Spring #Scope(value = "session", proxyMode=ScopedProxyMode.TARGET_CLASS) beans for objects that should be shared across a single Http-Session. This will provide for example one "Project" object for each User who is using my application.
To get this working I had to implement an interceptor for Hibernate that is returning the name of the class:
public class EntityProxySupportHibernateInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 7470168733867103334L;
#Override
public String getEntityName(Object object) {
return AopUtils.getTargetClass(object).getName();
}
}
With this interceptor I can use a Spring CrudRepository to save a Project-entity in the database:
#Repository
public interface ProjectRepository extends CrudRepository<Project, Integer> {
Project findByProjectId(int projectId);
}
Project-entity:
#Component
#Entity
#Table(name = "xxx.projects")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
public class Project implements Serializable {
private static final long serialVersionUID = -8071542032564334337L;
private int projectId;
private int projectType;
#Id
#Column(name = "project_id")
public int getProjectId() {
return projectId;
}
public void setProjectId(int projectId) {
this.projectId = projectId;
}
#Column(name = "project_type")
public int getProjectType() {
return projectType;
}
public void setProjectType(int projectType) {
this.projectType = projectType;
}
}
Storing the Project in the database works as expected. I can have a look at the database and the correct values are inserted. Now I have a different entity that I'm creating the same way as the project and that I want to save in the database via a CrudRepository.
Here the problem begins. Hibernate is not inserting the values that I have set. Hibernate always only inserts null into the database. Reading the values in my Spring application is working as expected. I think that Hibernate is not using the proxy of the entity but the underlying blueprint of the object. How can I force Hibernate to use the proxy with the correct values?
Repository:
#Repository("DataInput001Repository")
public interface DataInputRepository extends CrudRepository<DataInput, DataInputId> {}
Entity:
#Component("DataInput001")
#Entity
#Table(name = "xx.data_input_001")
#Scope(value="session", proxyMode=ScopedProxyMode.TARGET_CLASS)
#IdClass(DatanputId.class)
public class DataInput implements Serializable {
private static final long serialVersionUID = -6941087210396795612L;
#Id
#Column(name = "project_id")
private int projectId;
#Column(name = "income")
private String income;
#Column(name = "income_increase")
private String incomeIncrease;
/* Getter + Setter */
}
Service:
#Service("DataInputService001")
public class DataInputServiceImpl implements DataInputService {
#Resource(name = "DataInputMapper001")
DataInputMapperImpl dataInputMapper;
#Resource(name = "DataInput001Repository")
DataInputRepository dataInputRepository;
#Resource(name = "DataInput001")
DataInput datanInput;
#Transactional
public void createDataInput(String json) throws Exception {
dataInputMapper.mapDataInput(json);
dataInputRepository.save(dataInput);
}
public DataInput getDataInput() {
return dataInput;
}
public void setDataInput(DataInput dataInput) {
this.dataInput = dataInput;
}
}
I have searched online and tried some suggested corrections but my app won't run . It throws no property for type exception as well as UnsatisfiedDependencyException: Error creating bean.
When trying to run the code , it says Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.math.BigDecimal com.Wallet.Repository.TransactionRepository.getBalance(java.lang.Long)!No property getBalance found for type Transaction
This is the model code :
#Entity
public class Transaction {
#Id
#GeneratedValue
private Long id;
private BigDecimal amount;
private java.util.Date transactionDate;
private Long transactionReference;
private String details;
#ManyToOne
private UserWallet wallet;
public Transaction() {
super();
}
Repository
#Repository
public interface TransactionRepository extends CrudRepository <Transaction, Long>
{
Optional<Transaction> getTransactionByRef(Long reference)throws UserNotFoundException;
BigDecimal balanceByUserAccountID(Long accountId);
List<Transaction> transactionsByUserAccountID(Long Id)throws UserNotFoundException;
Transaction createTransaction(Transaction transaction) throws LowBalanceException;
BigDecimal getBalance(Long id);
}
ServiceImpl
#Service
public class TransactionServiceImpl {
#Autowired
private TransactionRepository transRepo;
public Object transactionByRef(Long reference) throws UserNotFoundException {
return transRepo.getTransactionByRef(reference).orElseThrow(
() -> new UserNotFoundException(String.format("transaction with ref '%d' doesnt exist", reference)));
}
#Transactional
public Transaction createTransaction(Transaction transaction) throws LowBalanceException {
BigDecimal balance = transRepo.getBalance(transaction.getWallet().getId());
if (balance.add(transaction.getAmount()).compareTo(BigDecimal.ZERO) >= 0) {
return transRepo.save(transaction);
}
throw new LowBalanceException(String.format("user's balance is %.2f and cannot perform a transaction of %.2f ",
balance.doubleValue(), transaction.getAmount().doubleValue()));
}
public BigDecimal balanceByUserAccountID(Long accountId) {
return transRepo.getBalance(accountId);
}
public Iterable<Transaction> getList() {
return transRepo.findAll();
}
public Optional<Transaction> transactionsByUserAccountID(Long txnRef) throws UserNotFoundException {
return transRepo.getTransactionByRef(txnRef);
}
public Iterable<Transaction> transactions() {
return transRepo.findAll();
}
}
Controller
#RestController
#RequestMapping("v1/addMoney")
public class TransactionController {
#Autowired
private UserAccountServiceImp userRepo;
#Autowired
private TransactionServiceImpl transactRepo;
#PostMapping("/{id}")
public ResponseEntity addMoney(#PathVariable("id") Long userAccountId, #RequestBody TransactionDTO walletDTO) {
Transaction saved;
try {
walletDTO.setUserAccountId(userAccountId);
saved = transactRepo.createTransaction(TransactionMapper.dtoToDO(walletDTO));
} catch (LowBalanceException ex) {
Logger.getLogger(UserController.class.getName()).log(Level.SEVERE, null, ex);
return new ResponseEntity<String>(ex.getMessage(), HttpStatus.BAD_REQUEST);
} catch (Exception ex) {
Logger.getLogger(UserController.class.getName()).log(Level.SEVERE, null, ex);
return new ResponseEntity<String>(ex.getMessage(), HttpStatus.BAD_REQUEST);
}
return new ResponseEntity<TransactionDTO>(TransactionMapper.doToDTO(saved), HttpStatus.CREATED);
}
#GetMapping("v1/balance/{id}")
public BigDecimal getBalance(#PathVariable Long userAccountId){
return transactRepo.balanceByUserAccountID(userAccountId);
}
}
A Spring Data Repository does a lot of magic under the hood. If you have a method called getBalance defined in the repository interface, it expects that you're trying to get the value of column balance from the entity called Transaction.
Simply add a field called balance in your entity class and you should be good:
#Entity
public class Transaction {
#Id
#GeneratedValue
private Long id;
private BigDecimal amount;
private java.util.Date transactionDate;
private Long transactionReference;
private String details;
private BigDecimal balance;
#ManyToOne
private UserWallet wallet;
public Transaction() {
super();
}
I want to get some data from Neo4j using Spring boot, but I always get nothing. In other words, it seems that java cannot get the data from the neo4j database.
The code is written according to the neo4j Spring tutorial.https://neo4j.com/developer/spring-data-neo4j/
domain class
#NodeEntity
public class Weather {
#Id
#GeneratedValue
private Long id;
private String name;
#Relationship(type = "HAS_INSTANCE")
private List<Instance> instances = new ArrayList<>();
#Relationship(type = "HAS_CHARACTERISTIC")
private List<Characteristic> characteristics = new ArrayList<>();
...
}
repository class
#RepositoryRestResource(collectionResourceRel = "weathers", path = "weathers")
public interface WeatherRepository extends Neo4jRepository<Weather, Long> {
}
service class
#Service
public class WeatherService {
private final WeatherRepository weatherRepository;
public WeatherService(WeatherRepository weatherRepository){
this.weatherRepository = weatherRepository;
}
#Transactional(readOnly = true)
public Iterable<Weather> findAll(){
Iterable<Weather> result = weatherRepository.findAll();
return result;
}
}
controller class
#RestController
#RequestMapping("/")
public class WeatherController {
private final WeatherService weatherService;
public WeatherController(WeatherService weatherService){
this.weatherService = weatherService;
}
#GetMapping("/findAll")
public Iterable<Weather> findAll(){
return weatherService.findAll();
}
}
And the username and password configuration are in the application.properties.
Could someone help me about it? Thanks!
I do everything according to http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html, but neither in-bean methods nor external ones are ever executed. What might be the cause?
#Entity
#EntityListeners(EntityListener.class)
public class User {
#Id
#Column
#GeneratedValue
private Integer id;
// etc...
#PostConstruct
#PostLoad
#PostPersist
#PostUpdate
public void magic() {
System.out.println("YES I AM EXECUTED!");
System.exit(123);
}
}
OR
#Entity
#EntityListeners(MyListener.class)
public class User {
#Id
#Column
#GeneratedValue
private Integer id;
// etc...
}
+
public class MyListener {
#PostPersist
void postPersist(Object object) {
System.out.println("CAN'T BELEIVE I SEE THIS!");
System.exit(234);
}
}
My code creates, saves and loads beans, but nothing happens on the listeners. This is a piece of the repository thats perform the operations:
#Repository
public class UserRepositoryImpl implements UserRepository {
#Autowired
private SessionFactory sessionFactory;
#Override
public User get(Integer id) {
return sessionFactory.getCurrentSession().get(User.class, id);
}
#Override
public User save(User user) {
Session session = sessionFactory.getCurrentSession();
user = (User) session.merge(user);
session.saveOrUpdate(user);
return user;
}
// etc...
}
Repository methods are called from services like this one:
#Service
#Transactional
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Override
public void something() {
// just some repo calls + extra logic
}
}
I do not think I got something special here.
JPA interceptors mechanism work only when you manipulate entities via JPA EntityManager, they have no effect when you're using Hibernate Session directly.
You'll have to implement Hibernate native interceptors if you want to use the Session API.