Skip to content

frankkwabenaaboagye/spring-mini-apps

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 

Repository files navigation

General Info

To import into your IDE, import the parent pom lab/pom.xml as Maven projects or lab/build.gradle as Gradle projects.

Note

Some Tips

  • credit: spring docs, spring academy, spring team

Content

  1. Configuration
  2. Component Scanning
  3. Spring Container
  4. AOP
  5. JDBC
  6. Transaction
  7. SpringBoot
  8. SpringBoot Testing
  9. Securing REST Application with Spring Security
  10. Actuator

Configuration

TODO

Component Scanning

TODO

Spring Container

TODO

AOP

  • There are some generic functionalities that are needed in many places in any application code
  • With this in mind, there is the need to avoid code tangling and eliminate code scattering
  • what can we do? Modularize🧩? Yes!
  • AOP helps to do that 💡
  • AOP Technologies - AspectJ, Spring AOP
  • Core concepts
    • Join Point: A point in the execution of a program - Method calls or exception
    • Pointcut : Expression that selects one or more join point
    • Advice : Code to be executed at each selected join point
    • Aspect : Module that encapsulated Pointcuts & Advice
    • Weaving : Combining Aspects with main code
  • Defining a pointcut
    • Spring AOP uses AspectJ expression language for selecting where to apply advice
    • When defining pointcut, we're defining designators
    • Designators - execution, e.t.c
// format
execution( <method pattern> )

// chain together with &&, ||, !
execution( <pattern 1> ) && execution( <pattern 2> )

// method pattern
[modifiers] ReturnType [ClassType] MethodName(Arguments) [throws ExceptionType]
    // two mandatory things:
        // ReturnType and
        // MethodName with Arguments
  • Advice Types
    • Before
      • Proxy delegates to the advice before delegating to the target⚙️
      • do whatever you want in the advice before executing the business logic
      • if you happen to put an exception on the advice, you prevent the target from executing
        • good for security🔒 use cases
    • AfterReturning ↩️
      • Proxy first delegates to the target⚙️
      • then the proxy delegates to the advice
        • This only happens when there is a successful return from the target
        • there will be more information here because, you will have
          • the context information
          • with the return from the target
    • AfterThrowing ⚠️
      • Proxy first delegate to the target⚙️
      • then the proxy delegates to the advice
        • This only happens when there is an exception thrown from the target
        • you will have the exception available to you
        • you can throw another exception or allow it to propagate
          • there is a work around this
    • After 🔚
      • Proxy delegate to the target⚙️
      • then proxy delegate to the advice
      • whether there is a success reutrn or exception, it does not matter
      • advice is excuted after the target⚙️
    • Around 🔄
      • Proxy delegates to the advice
      • it is your responsibility to call proceed method to the target⚙️
      • in this way, you can excute things before and after the target - cool! 😎
  • Limitations of spring aop
    • can only advise non-private methods
    • can only advise spring beans
    • inner method call inside of a target would not get advised
  • Note: AspectJ does not have this limitations

JDBC

  • There are issues with plain Jdbc
    • boilerplate code
    • forced to catch certain exceptions
    • forced to close resources
  • Spring JDBC Template solves these issues
    • with a simple statement, spring will be able to handle for us
      • connection
      • statement execution
      • result set processsing
      • exceptions
      • release of connection
  • Note: When creating the Jdbc, we need a datasource
  • Basic Usage
    • For simple Types
    • For Generic Maps
    • For Domain Objects
// for simple types

jdbcTemplate.queryForObject(the_sql, the_return_class);

String sql = "select count(*) from PERSON";
jdbcTemplate.queryForObject(sql, Long.class);

    // you can bind variables too
    String sql = "insert into PERSON(first_name, last_name) values(?, ?)";
    jdbcTemplate.update(sql, "Kay", "Lee");
        /* Note: Use the `update` method
             to perfom - insert, update, delete
        */

// for generic maps
    // can return each row of a ResultSet as a map
jdbcTemplate.queryForList(...);
jdbcTemplate.queryForMap(...);   // watch out for memory consumption

    // example
    String sql = "select * from PERSON where id=?";
    int id = 1;
    jdbcTemplate.queryForMap(sql, id);  // this returns: Map<String, Object>

    String sql = "select * from PERSON";
    jdbcTemplate.queryForList(sql); // returns: List<Map<String, Object>>


// Domain Object queries
    // you have to use call back approach
        - RowMapper
        - ResultSetExtractor
        - RowCallbackHandler


jdbcTemplate
.queryForObject(
    String sql,
    RowMapper<T> rowMapper,
    Object... args
)
.queryForObject(
    String sql,
    RowMapper<T> rowMapper
)


    // examples
        // single row
            String sql = "select first_name, last_name from PERSON where id=?";

                // here we define row mapper using lambda
            jdbc.queryForObject(
                sql,
                (rs, rowNum)-> new Person(rs.getString("first_name"), rs.getString("last_name")),
                id
            );
                // the above returns `Person` domain object

        // multiple rows
            String sql = "select first_name, last_name from PERSON";

                // here we define row mapper using lambda also
            jdbc.queryForObject(
                sql,
                (rs, rowNum)-> new Person(rs.getString("first_name"), rs.getString("last_name"))
            );
                // the above returns `List<Person>` domain object

// Examples

// before - plain jdbc
public Restaurant findByMerchantNumber(String merchantNumber) {
    String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE, BENEFIT_AVAILABILITY_POLICY"
            + " from T_RESTAURANT where MERCHANT_NUMBER = ?";
    Restaurant restaurant = null;

    try (Connection conn = dataSource.getConnection();
            PreparedStatement ps = conn.prepareStatement(sql) ){
        ps.setString(1, merchantNumber);
        ResultSet rs = ps.executeQuery();
        advanceToNextRow(rs);
        restaurant = mapRestaurant(rs);
    } catch (SQLException e) {
        throw new RuntimeException("SQL exception occurred finding by merchant number", e);
    }

    return restaurant;
}

// after - refactored

	public Restaurant findByMerchantNumber(String merchantNumber) {
		String sql = "select MERCHANT_NUMBER, NAME, BENEFIT_PERCENTAGE, BENEFIT_AVAILABILITY_POLICY"
				+ " from T_RESTAURANT where MERCHANT_NUMBER = ?";

		Restaurant restaurant = jdbcTemplate.queryForObject(
            sql,
            (rs, rowNum) -> mapRestaurant(rs),
            merchantNumber
        );

		return restaurant;
	}

//--------------------------------

// before
public void updateBeneficiaries(Account account) {
    String sql = "update T_ACCOUNT_BENEFICIARY SET SAVINGS = ? where ACCOUNT_ID = ? and NAME = ?";
    Connection conn = null;
    PreparedStatement ps = null;
    try {
        conn = dataSource.getConnection();
        ps = conn.prepareStatement(sql);
        for (Beneficiary beneficiary : account.getBeneficiaries()) {
            ps.setBigDecimal(1, beneficiary.getSavings().asBigDecimal());
            ps.setLong(2, account.getEntityId());
            ps.setString(3, beneficiary.getName());
            ps.executeUpdate();
        }
    } catch (SQLException e) {
        throw new RuntimeException("SQL exception occurred updating beneficiary savings", e);
    } finally {
        if (ps != null) {
            try {
                // Close to prevent database cursor exhaustion
                ps.close();
            } catch (SQLException ex) {
            }
        }
        if (conn != null) {
            try {
                // Close to prevent database connection exhaustion
                conn.close();
            } catch (SQLException ex) {
            }
        }
    }
}

// after

public void updateBeneficiaries(Account account) {
    String sql = "update T_ACCOUNT_BENEFICIARY SET SAVINGS = ? where ACCOUNT_ID = ? and NAME = ?";


    for (Beneficiary beneficiary : account.getBeneficiaries()) {
        jdbcTemplate.update(sql, beneficiary.getSavings().asBigDecimal(), account.getEntityId(), beneficiary.getName());
    }

}

// utilise lambda

    // consider
    jdbcTemplate.queryForObject(sql, new RowMapper<Restaurant>() {
        @Override
        public Restaurant mapRow(ResultSet rs, int rowNum) throws SQLException {
            return mapRestaurant(rs);
        }
    });

    // same as

    jdbcTemplate.queryForObject(sql, (rs, rowNum) -> mapRestaurant(rs)); // much better


// another example

    // consider
    jdbcTemplate.query(
        sql,
        new ResultSetExtractor<Account>() {
			@Override
			public Account extractData(ResultSet rs) throws SQLException, DataAccessException {
				return mapAccount(rs);
			}
		},
        creditCardNumber
    );

    // same as
    jdbcTemplate.query(
        sql,
        rs -> {
            return mapAccount(rs);
        },
        creditCardNumber
    );

    // same as
    jdbcTemplate.query(
        sql,
        this::mapAccount,
        creditCardNumber
    );


// by the way; the mapAcount method is shown below
    /*
	private Account mapAccount(ResultSet rs) throws SQLException {
		Account account = null;
		while (rs.next()) {
			if (account == null) {
				String number = rs.getString("ACCOUNT_NUMBER");
				String name = rs.getString("ACCOUNT_NAME");
				account = new Account(number, name);
				// set internal entity identifier (primary key)
				account.setEntityId(rs.getLong("ID"));
			}
			account.restoreBeneficiary(mapBeneficiary(rs));
		}
		if (account == null) {
			// no rows returned - throw an empty result exception
			throw new EmptyResultDataAccessException(1);
		}
		return account;
	}
    */

Transaction

  • We have to adhere to the ACID {Atomicity, Consistency, Isolation, Durable} principles when it comes to the data access layer right?
  • When running non transactionally, there could be issues like:
    • separate connections for separate method calls
    • partial failures might be a problem too
  • we need deal with this
    • with transactions
      • we become very efficient, because same connection is used for each operation
      • also operations complete as an atomic unit
  • Spring Transaction Management
    1. Declare a PlatformTransactionManager bean
      • note that there are several implementations availbel
        • DataSourceTM, JmsTM, JpaTM , e.t.c.
    2. Declare the transactional methods
      • you can use annotations(recommended), or go the programmatic way, or even do both
    3. Add the @EnableTransactionManagement to a configuration class
// examples

	@Bean
	public DataSource dataSource(){
        ...
        ...
	}

	@Bean
	public PlatformTransactionManager transactionManager(){
		return new DataSourceTransactionManager(dataSource());
	}

    //----

    @Configuration
    @EnableTransactionManagement
    public class RewardsConfig {
        ...
        ...
    }

Spring Boot

In a Spring application, we typically need to:

  • Configure dependencies in the pom.xml file
  • Set up configurations in the application context

However, many of these components can be predicted, so why not let Spring Boot handle them for us?

  • Spring Boot automates low-level configurations

    • This requires it to make certain decisions (hence, it has its own opinions)
      • Essentially, Spring Boot takes an opinionated approach to the Spring framework and third-party libraries
      • However, we still have the ability to override these defaults.
    • Important note:
      • Spring Boot is not a code generator; all configurations happen at runtime.
  • Spring Boot features

    • dependency Management
      • pom file and starter dependencies
    • auto configuration
      • @SpringBootApplication
        • represents the combination of - @EnableAutoConfiguration, @ComponentScan, and @SpringBootConfiguration
    • packaging and runtime
    • integration testing
  • Getting started

    • 3 files needed
      • to set up spring boot and other dependencies - pom.xml
      • for general configuration - application.properties
      • the application launcher - application.class (entry point)
    • use : start.spring.io
    • or : spring-io/initializr on github
  • Spring Data Jpa

    • The Spring Data provides a consistent programming model across different data stores
    • It create Instant Repository
  • Web application

    • Spring MVC
      • lifecycle of a request
        • Request Lifecycle in Spring MVC
          • The Dispatcher Servlet is the core of Spring MVC and handles all incoming requests.
          • However, it doesn't process them directly.
          • It delegates the request to Handler Mapping, which identifies the appropriate controller for the request.
          • Once the controller is identified, the Dispatcher Servlet passes the request to the Handler Adapter.
            • The handler adapter adapts the request, passing necessary parameters (e.g., request data, URI variables) to the controller.
            • The Controller returns a response:
              • For server-side rendering, it returns a Model and View, where the view is a logical view.
              • The view is resolved by the View Resolver, which locates and creates the actual view instance.
            • Based on the model returned by the controller, the Dispatcher Servlet renders the view to the user.
          • If the controller returns an object (like JSON or XML), the Dispatcher Servlet delegates it to Message Converters.
            • The response from the message converters is then sent to the user.

Spring Boot Testing

  • @SpringBootTest registers a TestRestTemplate bean.
  • TestRestTemplate is, by design, fault tolerant.
    • This means that it does not throw exceptions when an error response (400 or greater) is received.
  • To test Spring MVC Controllers, you can use @WebMvcTest.
    • It auto-configures the Spring MVC infrastructure (and nothing else) for the web slice tests.
  • Note that the Web slice testing runs faster than an integration testing since it does not need to start a server.
BDDMockito: given(..), willReturn(..), willThrow(..)
MockMvc: perform(..)
ResultActions: andExpect(..)
MockMvcRequestBuilders: get(..), post(..), put(..), delete(..)
MockMvcResultMatchers: status(), content(), jsonPath(..), header()
  • Note that @WebMvcTest auto-configures MockMvc bean.
// example
	@Test
	public void accountDetails() throws Exception {

		given(accountManager.getAccount(0L))
				.willReturn(new Account("1234567890", "John Doe"));

		mockMvc.perform(get("/accounts/0"))
			   .andExpect(status().isOk())
			   .andExpect(content().contentType(MediaType.APPLICATION_JSON))
			   .andExpect(jsonPath("name").value("John Doe"))
			   .andExpect(jsonPath("number").value("1234567890"));

		verify(accountManager).getAccount(0L);

	}

Securing REST Application with Spring Security

Actuator