Una aplicació bancària gestiona els comptes bancaris dels clients. Prova la funcionalitat
de les diferents capes de la implementació fent les proves següents:
Proves unitàries de la capa de servei, amb objectes simulats de Mockito per a la capa de repositori.
Proves unitàries de la capa de repositori, amb objectes simulats de Mockito per a la capa DAO.
Proves unitàries de la capa DAO, amb una base de dades incrustada H2.
Proves d'integració des de la capa de servei fins a la capa DAO, amb una
base de dades incrustada H2.
packageud8.bank.domain.service.impl;importud8.common.exception.ResourceNotFoundException;importud8.bank.domain.entity.BankAccount;importud8.bank.domain.service.BankAccountService;importud8.bank.domain.service.NotificationService;importud8.bank.persistance.repository.BankAccountRepository;publicclassBankAccountServiceImplimplementsBankAccountService{privatefinalBankAccountRepositoryrepository;privatefinalNotificationServicenotificationService;publicBankAccountServiceImpl(BankAccountRepositoryrepository,NotificationServicenotificationService){this.repository=repository;this.notificationService=notificationService;}@OverridepublicBankAccountfindByIBAN(Stringiban)throwsResourceNotFoundException{BankAccountbank=repository.findByIBAN(iban);if(bank==null)thrownewResourceNotFoundException("Bank account not found");returnbank;}@OverridepublicBankAccountcreate(){StringnewIBAN=generateIBAN();BankAccountaccount=newBankAccount(newIBAN);repository.save(account);notificationService.sendNotification(account,"Your new bank account has been created!");returnaccount;}publicStringgenerateIBAN(){BankAccountlatest=repository.latest();intaccountNumber=latest==null?1:latest.getAccountNumber()+1;returnString.format("ES%0"+BankAccount.ACCOUNT_NUMBER_LENGTH+"d",accountNumber);}@Overridepublicbooleanupdate(BankAccountbank){if(!repository.existsByIBAN(bank.getIban())){returnfalse;}repository.save(bank);returntrue;}@Overridepublicbooleandelete(Stringiban){if(!repository.existsByIBAN(iban)){returnfalse;}repository.delete(iban);returntrue;}@Overridepublicbooleandeposit(BankAccountaccount,doubleamount){if(amount<=0){returnfalse;}account.setBalance(account.getBalance()+amount);repository.save(account);returntrue;}@Overridepublicbooleanwithdraw(BankAccountaccount,doubleamount){if(amount<=0||account.getBalance()<amount){returnfalse;}account.setBalance(account.getBalance()-amount);repository.save(account);returntrue;}@Overridepublicbooleantransfer(BankAccountfrom,BankAccountto,doubleamount){if(amount<=0||from.getBalance()<amount){returnfalse;}from.setBalance(from.getBalance()-amount);to.setBalance(to.getBalance()+amount);repository.save(from);repository.save(to);notificationService.sendNotification(from,"Transfer of "+amount+" to "+to.getIban());notificationService.sendNotification(to,"Transfer of "+amount+" from "+from.getIban());returntrue;}}
packageud8.bank.persistance.dao.impl;importud8.bank.domain.entity.BankAccount;importud8.bank.persistance.dao.BankAccountDao;importud8.bank.persistance.rowmapper.BankAccountRowMapper;importud8.common.persistance.database.DatabaseConnection;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;publicclassBankAccountDaoJdbcimplementsBankAccountDao{privatefinalDatabaseConnectionconnection;privatefinalBankAccountRowMapperbankAccountRowMapper;publicBankAccountDaoJdbc(){this.connection=DatabaseConnection.getInstance();this.bankAccountRowMapper=newBankAccountRowMapper();}@OverridepublicBankAccountfindByIBAN(Stringiban){Stringsql="SELECT * FROM bank_account WHERE iban = ?";try(PreparedStatementpreparedStatement=connection.prepareStatement(sql)){preparedStatement.setString(1,iban);ResultSetrs=preparedStatement.executeQuery();if(!rs.next())returnnull;returnbankAccountRowMapper.mapRow(rs);}catch(SQLExceptione){thrownewRuntimeException(e.getMessage());}}@OverridepublicBankAccountlatest(){Stringsql="SELECT iban, balance FROM bank_account ORDER BY iban DESC LIMIT 1";try(PreparedStatementpreparedStatement=connection.prepareStatement(sql)){ResultSetrs=preparedStatement.executeQuery();if(!rs.next())returnnull;returnbankAccountRowMapper.mapRow(rs);}catch(SQLExceptione){thrownewRuntimeException(e.getMessage());}}@Overridepublicintinsert(BankAccountbank){Stringsql="INSERT INTO bank_account (iban, balance) VALUES (?, ?)";try(PreparedStatementpreparedStatement=connection.prepareStatement(sql)){preparedStatement.setString(1,bank.getIban());preparedStatement.setDouble(2,bank.getBalance());returnpreparedStatement.executeUpdate();}catch(SQLExceptione){thrownewRuntimeException(e.getMessage());}}@Overridepublicintupdate(BankAccountbank){Stringsql="UPDATE bank_account SET balance = ? WHERE iban = ?";try(PreparedStatementpreparedStatement=connection.prepareStatement(sql)){preparedStatement.setDouble(1,bank.getBalance());preparedStatement.setString(2,bank.getIban());returnpreparedStatement.executeUpdate();}catch(SQLExceptione){thrownewRuntimeException(e.getMessage());}}@Overridepublicintdelete(Stringiban){Stringsql="DELETE FROM bank_account WHERE iban = ?";try(PreparedStatementpreparedStatement=connection.prepareStatement(sql)){preparedStatement.setString(1,iban);returnpreparedStatement.executeUpdate();}catch(SQLExceptione){thrownewRuntimeException(e.getMessage());}}}
packageud8.common.persistance.database;/* * Added additional null checks when closing the ResultSet and Statements. * * Thanks to pihug12 and Grzegorz Oledzki at stackoverflow.com * http://stackoverflow.com/questions/5332149/jdbc-scriptrunner-java-lang-nullpointerexception?tab=active#tab-top *//* * Modified: Use logWriter in print(Object), JavaDoc comments, correct Typo. *//* * Modified by Pantelis Sopasakis <chvng@mail.ntua.gr> to take care of DELIMITER statements. This way you * can execute scripts that contain some TRIGGER creation code. New version using REGEXPs! Latest * modification: Cater for a NullPointerException while parsing. Date: Feb 16, 2011, 11:48 EET *//* * Slightly modified version of the com.ibatis.common.jdbc.ScriptRunner class from the iBATIS Apache * project. Only removed dependency on Resource class and a constructor *//* * Copyright 2004 Clinton Begin Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */importjava.io.IOException;importjava.io.LineNumberReader;importjava.io.PrintWriter;importjava.io.Reader;importjava.sql.Connection;importjava.sql.ResultSet;importjava.sql.ResultSetMetaData;importjava.sql.SQLException;importjava.sql.Statement;importjava.util.regex.Matcher;importjava.util.regex.Pattern;/** * Tool to run database scripts. This version of the script can be found at * https://gist.github.com/gists/831762/ */publicclassScriptRunner{privatestaticfinalStringDEFAULT_DELIMITER=";";privatestaticfinalStringDELIMITER_LINE_REGEX="(?i)DELIMITER.+";privatestaticfinalStringDELIMITER_LINE_SPLIT_REGEX="(?i)DELIMITER";privatefinalConnectionconnection;privatefinalbooleanstopOnError;privatefinalbooleanautoCommit;privatePrintWriterlogWriter=newPrintWriter(System.out);privatePrintWritererrorLogWriter=newPrintWriter(System.err);privateStringdelimiter=DEFAULT_DELIMITER;privatebooleanfullLineDelimiter=false;/** * Default constructor. * * @param connection * @param autoCommit * @param stopOnError */publicScriptRunner(Connectionconnection,booleanautoCommit,booleanstopOnError){this.connection=connection;this.autoCommit=autoCommit;this.stopOnError=stopOnError;}/** * @param delimiter * @param fullLineDelimiter */publicvoidsetDelimiter(Stringdelimiter,booleanfullLineDelimiter){this.delimiter=delimiter;this.fullLineDelimiter=fullLineDelimiter;}/** * Setter for logWriter property. * * @param logWriter * - the new value of the logWriter property */publicvoidsetLogWriter(PrintWriterlogWriter){this.logWriter=logWriter;}/** * Setter for errorLogWriter property. * * @param errorLogWriter * - the new value of the errorLogWriter property */publicvoidsetErrorLogWriter(PrintWritererrorLogWriter){this.errorLogWriter=errorLogWriter;}/** * Runs an SQL script (read in using the Reader parameter). * * @param reader * - the source of the script * @throws SQLException * if any SQL errors occur * @throws IOException * if there is an error reading from the Reader */publicvoidrunScript(Readerreader)throwsIOException,SQLException{try{booleanoriginalAutoCommit=connection.getAutoCommit();try{if(originalAutoCommit!=autoCommit){connection.setAutoCommit(autoCommit);}runScript(connection,reader);}finally{connection.setAutoCommit(originalAutoCommit);}}catch(IOExceptione){throwe;}catch(SQLExceptione){throwe;}catch(Exceptione){thrownewRuntimeException("Error running script. Cause: "+e,e);}}/** * Runs an SQL script (read in using the Reader parameter) using the connection passed in. * * @param conn * - the connection to use for the script * @param reader * - the source of the script * @throws SQLException * if any SQL errors occur * @throws IOException * if there is an error reading from the Reader */privatevoidrunScript(Connectionconn,Readerreader)throwsIOException,SQLException{StringBuffercommand=null;try{LineNumberReaderlineReader=newLineNumberReader(reader);Stringline=null;while((line=lineReader.readLine())!=null){if(command==null){command=newStringBuffer();}StringtrimmedLine=line.trim();if(trimmedLine.startsWith("--")){println(trimmedLine);}elseif(trimmedLine.length()<1||trimmedLine.startsWith("//")){// Do nothing}elseif(trimmedLine.length()<1||trimmedLine.startsWith("--")){// Do nothing}elseif(!fullLineDelimiter&&trimmedLine.endsWith(getDelimiter())||fullLineDelimiter&&trimmedLine.equals(getDelimiter())){Patternpattern=Pattern.compile(DELIMITER_LINE_REGEX);Matchermatcher=pattern.matcher(trimmedLine);if(matcher.matches()){setDelimiter(trimmedLine.split(DELIMITER_LINE_SPLIT_REGEX)[1].trim(),fullLineDelimiter);line=lineReader.readLine();if(line==null){break;}trimmedLine=line.trim();}command.append(line.substring(0,line.lastIndexOf(getDelimiter())));command.append(" ");Statementstatement=conn.createStatement();println(command);booleanhasResults=false;if(stopOnError){hasResults=statement.execute(command.toString());}else{try{statement.execute(command.toString());}catch(SQLExceptione){e.fillInStackTrace();printlnError("Error executing: "+command);printlnError(e);}}if(autoCommit&&!conn.getAutoCommit()){conn.commit();}ResultSetrs=statement.getResultSet();if(hasResults&&rs!=null){ResultSetMetaDatamd=rs.getMetaData();intcols=md.getColumnCount();for(inti=0;i<cols;i++){Stringname=md.getColumnLabel(i);print(name+"\t");}println("");while(rs.next()){for(inti=1;i<=cols;i++){Stringvalue=rs.getString(i);print(value+"\t");}println("");}}command=null;try{if(rs!=null){rs.close();}}catch(Exceptione){e.printStackTrace();}try{if(statement!=null){statement.close();}}catch(Exceptione){e.printStackTrace();// Ignore to workaround a bug in Jakarta DBCP}}else{Patternpattern=Pattern.compile(DELIMITER_LINE_REGEX);Matchermatcher=pattern.matcher(trimmedLine);if(matcher.matches()){setDelimiter(trimmedLine.split(DELIMITER_LINE_SPLIT_REGEX)[1].trim(),fullLineDelimiter);line=lineReader.readLine();if(line==null){break;}trimmedLine=line.trim();}command.append(line);command.append(" ");}}if(!autoCommit){conn.commit();}}catch(SQLExceptione){e.fillInStackTrace();printlnError("Error executing: "+command);printlnError(e);throwe;}catch(IOExceptione){e.fillInStackTrace();printlnError("Error executing: "+command);printlnError(e);throwe;}finally{conn.rollback();flush();}}privateStringgetDelimiter(){returndelimiter;}privatevoidprint(Objecto){if(logWriter!=null){logWriter.print(o);}}privatevoidprintln(Objecto){if(logWriter!=null){logWriter.println(o);}}privatevoidprintlnError(Objecto){if(errorLogWriter!=null){errorLogWriter.println(o);}}privatevoidflush(){if(logWriter!=null){logWriter.flush();}if(errorLogWriter!=null){errorLogWriter.flush();}}}
Aquestes classes són comunes als exemples i exercicis d'aquesta unitat.
AppPropertiesReader.java
packageud8.common.utils;importjava.io.IOException;importjava.io.InputStream;importjava.util.Properties;publicclassAppPropertiesReader{privatestaticfinalPropertiesproperties=newProperties();static{loadProperties("application.properties");// Carga las propiedades por defecto// Detectar el perfil y cargar las propiedades correspondientesStringactiveProfile=getProperty("app.profiles.active");if(activeProfile!=null){loadProperties("application-"+activeProfile+".properties");}}privatestaticvoidloadProperties(Stringfilename){try(InputStreaminput=Thread.currentThread().getContextClassLoader().getResourceAsStream(filename)){if(input==null){System.out.println("No s'ha pogut trobar el fitxer de configuració: "+filename);return;}// Cargar las propiedades desde el archivo de configuraciónproperties.load(input);}catch(IOExceptione){System.err.println("Error al llegir el fitxer de configuració: "+filename);}}publicstaticStringgetProperty(Stringkey){returnproperties.getProperty(key);}}