Spring framework - Part 2

Keywords: Programming Spring SQL xml JDBC

Chapter 1: Spring's IOC implementation of CRUD

1.1 - Requirements and technical requirements

  • Requirement: realize CRUD operation of account
  • Technology:
    • Using spring's IoC to manage objects
    • Using dbutils as a persistence layer solution
    • Using c3p0 data source

1.2 - environment construction

1.2.1-Maven project import dependency

  <dependencies>
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--dbUtils-->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.4</version>
    </dependency>
    <!--c3p0-->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.26</version>
    </dependency>
  </dependencies>

1.2.2 - database script

CREATE DATABASE  IF NOT EXISTS db1
USE db1
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(40),
money FLOAT
)CHARACTER SET utf8 COLLATE utf8_general_ci;
INSERT INTO account(NAME,money) VALUES('aaa',1000);
INSERT INTO account(NAME,money) VALUES('bbb',1000);
INSERT INTO account(NAME,money) VALUES('ccc',1000);

1.2.3 - create entity class

public class Account {
  private int id;
  private String name;
  private float money;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public float getMoney() {
    return money;
  }

  public void setMoney(float money) {
    this.money = money;
  }

  @Override
  public String toString() {
    return "Account{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", money=" + money +
            '}';
  }
}

1.2.4 - create persistence layer

Interface

public interface IAccountDao {
  /**
   * Query all account information
   *
   * @return
   */
  List<Account> findAll();

  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * Add a new data
   * @param account
   */
  void save(Account account);

  /**
   * To update
   * @param account
   */
  void update(Account account);

  /**
   * delete
   * @param id
   */
  void del(int id);
}

Implementation class

public class AccountDaoImpl implements IAccountDao {
  private QueryRunner runner = null;
  public void setRunner(QueryRunner runner) {
    this.runner = runner;
  }

  @Override
  public List<Account> findAll() {
    try{
      String sql = "select * from account";
      return runner.query(sql,new BeanListHandler<Account>(Account.class));
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public Account findOne(int id) {
    try{
      String sql = "select * from account where id=?";
      return runner.query(sql,new BeanHandler<>(Account.class),id);
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public void save(Account account) {
    try{
      String sql = "INSERT INTO account(NAME,money) VALUES(?,?)";
      runner.update(sql,account.getName(),account.getMoney());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void update(Account account) {
    try{
      String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
      runner.update(sql,account.getName(),account.getMoney(),account.getId());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void del(int id) {
    try{
      String sql = "DELETE FROM account WHERE id=?";
      runner.update(sql,id);
    }catch (Exception e){
      e.printStackTrace();
    }
  }

}

1.2.5 - create business layer

Interface

public interface IAccountServices {
  /**
   * Query all account information
   *
   * @return
   */
  List<Account> findAll();

  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * Add a new data
   * @param account
   */
  void save(Account account);

  /**
   * To update
   * @param account
   */
  void update(Account account);

  /**
   * delete
   * @param id
   */
  void del(int id);
}

Implementation class

public class AccountServicesImpl implements IAccountServices {
  private IAccountDao dao = null;
  public void setDao(IAccountDao dao) {
    this.dao = dao;
  }

  @Override
  public List<Account> findAll() {
    return dao.findAll();
  }

  @Override
  public Account findOne(int id) {
    return dao.findOne(id);
  }

  @Override
  public void save(Account account) {
    dao.save(account);
  }

  @Override
  public void update(Account account) {
    dao.update(account);
  }

  @Override
  public void del(int id) {
    dao.del(id);
  }
}

1.2.6 - create profile

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
</beans>

1.3 - configuration steps

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--To configure IAccountService object-->
    <bean id="accountService" class="cn.lpl666.services.impl.AccountServicesImpl">
        <property name="dao" ref="accountDao"></property>
    </bean>
    <!--To configure IAccountDao object-->
    <bean id="accountDao" class="cn.lpl666.dao.impl.AccountDaoImpl">
        <property name="runner" ref="queryRunner"></property>
    </bean>
    <!--To configure QueryRunner-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--Inject data source-->
        <constructor-arg name="ds" ref="c3p0"></constructor-arg>
    </bean>
    <!--Configure data sources-->
    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--Inject necessary information to connect to database-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>

1.4- test

public class ClientTest {
  // Create Spring container
  private ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
  // Get IAccountServices object
  private IAccountServices services = ac.getBean("accountService",IAccountServices.class);
  /**
   * Query all
   */
  @Test
  public void findAll(){
    List<Account> all = services.findAll();
    System.out.println(all);
  }

  /**
   * Query one
   */
  @Test
  public void findOne(){
    Account account = services.findOne(1);
    System.out.println(account);
  }

  /**
   * Add to
   */
  @Test
  public void save(){
    Account account = new Account();
    account.setName("test2");
    account.setMoney(40000);
    services.save(account);
  }

  /**
   * modify
   */
  @Test
  public void update(){
    Account account = services.findOne(1);
    account.setMoney(9999);
    services.update(account);
  }

  /**
   * delete
   */
  @Test
  public void del(){
    services.del(6);
  }

}

Chapter 2: IOC configuration based on annotation

2.1 - Environmental Construction

Maven introduces dependency package

  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>

Persistence layer interface and implementation class

Interface

public interface IAccountDao {
  void save();
}

Implementation class

@Component("accountDao")
public class AccountDaoImpl implements IAccountDao {
  @Override
  public void save() {
    System.out.println("Account saved");
  }
}

Service layer interface and implementation class

Interface

public interface IAccountServices {
  void save();
}

Implementation class

@Component("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {
  @Autowired
  @Qualifier("accountDao")
  private IAccountDao dao = null;
  @Value("10")
  private int number;
  @Override
  public void save() {
    dao.save();
    System.out.println(number);
  }
}

Configuration file bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 
		//Tell spring which packages to scan when creating containers 
		cn.lpl666 Package all dao Layer, service layer and entity class will be scanned
	-->
    <context:component-scan base-package="cn.lpl666"/>
</beans>

Test class

public class ClientUI {
  public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountServices services =(IAccountServices) ac.getBean("accountService");
    services.save();
  }
}

2.2 - Common notes

2.2.1 - for creating objects

Equivalent to: < bean id = "" class = "" >

@Component

Role: let spring manage the resources. This is equivalent to configuring a bean in xml.

Property: value, which specifies the id of the bean. If the value attribute is not specified, the default bean id is the class name of the current class. Initial lowercase

@Component("accountService")
public class AccountServicesImpl implements IAccountServices {}

@Controller, @Service, @Repository

Their three annotations as like as two peas of @Component, are all identical.

They just provide a clearer semantics.

@Controller: annotation commonly used for presentation layer.

@Service: generally used for annotation of business layer.

@Repository: annotation commonly used for persistence layer.

Details: if there is only one attribute to assign in the annotation, and the name is value, value can not be written in the assignment.

2.2.2 - for injection data

Equivalent to: < property name = "" value = "" > or < property name = "" ref = "" >

@Autowired

Function: automatically inject by type. The set method can be omitted when using annotation injection properties. It can only inject other bean types. When there are multiple types matching, use the name of the object variable to be injected as the id of the bean, find it in the spring container, and it can also be injected successfully. If you can't find it, report it as a mistake.

@Qualifier

Function: Based on the automatic injection according to the type, it is injected according to the id of the Bean. It can't be used independently when injecting fields, it must be used together with @ Autowire; however, it can be used independently when injecting method parameters.

Property: value: Specifies the id of the bean.

@Component("accountService")
public class AccountServicesImpl implements IAccountServices {
  @Autowired
  @Qualifier("accountDao")
  private IAccountDao dao = null;
)

@Resource

Function: inject directly according to bean's id. It can only inject other bean types.

Property: name, which specifies the id of the bean.

@Component("accountService")
public class AccountServicesImpl implements IAccountServices {
  @Resource(name="accountDao")
  private IAccountDao dao = null;
)

@Value

Function: inject basic data type and String type data

Property: value, used to specify a value

  @Value("10")
  private int number;

2.2.3 - for changing the range of action

Equivalent to: < bean id = "" class = "" scope = "" >

@Scope

Role: Specifies the scope of the bean.

Property: value, which specifies the value of the range.

  • Value: single prototype request session global session
@Component("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {}

2.2.4 - life cycle related

Equivalent to: < bean id = "" class = "" init method = "" destroy method = "" >

@PostConstruct

Function: used to specify the initialization method.

@PreDestroy

Role: used to specify the destruction method

2.2.5 - annotation and XML selection

Advantages of annotation: simple configuration and convenient maintenance (we find the class, which is equivalent to finding the corresponding configuration).

The advantage of XML: when you modify it, you don't need to change the source code. No recompilation and deployment involved

Comparison of Spring's Bean management methods:

2.3 - annotate IOC to implement CRUD

2.3.1 - Requirements and technical requirements

Ditto chapter I: 1.1 - Requirements and technical requirements

2.3.2-Maven project import dependency

<dependencies>
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--dbUtils-->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.4</version>
    </dependency>
    <!--c3p0-->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--spring-test-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.3.RELEASE</version>
      <scope>test</scope>
    </dependency>

    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.26</version>
    </dependency>

  </dependencies>

2.3.3 - database script

Ditto chapter I: 1.2.2 - database script

2.3.4 - create entity class

Ditto Chapter 1: 1.2.3 - create entity class

2.3.5 - create persistence layer

Interface:

public interface IAccountDao {
  /**
   * Query all account information
   *
   * @return
   */
  List<Account> findAll();

  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * Add a new data
   * @param account
   */
  void save(Account account);

  /**
   * To update
   * @param account
   */
  void update(Account account);

  /**
   * delete
   * @param id
   */
  void del(int id);
}

Implementation class:

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
  @Autowired
  private QueryRunner runner = null;

  @Override
  public List<Account> findAll() {
    try{
      String sql = "select * from account";
      return runner.query(sql,new BeanListHandler<Account>(Account.class));
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public Account findOne(int id) {
    try{
      String sql = "select * from account where id=?";
      return runner.query(sql,new BeanHandler<>(Account.class),id);
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public void save(Account account) {
    try{
      String sql = "INSERT INTO account(NAME,money) VALUES(?,?)";
      runner.update(sql,account.getName(),account.getMoney());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void update(Account account) {
    try{
      String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
      runner.update(sql,account.getName(),account.getMoney(),account.getId());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void del(int id) {
    try{
      String sql = "DELETE FROM account WHERE id=?";
      runner.update(sql,id);
    }catch (Exception e){
      e.printStackTrace();
    }
  }

}

2.3.6 - create business layer

Interface:

public interface IAccountServices {
  /**
   * Query all account information
   *
   * @return
   */
  List<Account> findAll();

  /**
   * Query a data according to id
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * Add a new data
   * @param account
   */
  void save(Account account);

  /**
   * To update
   * @param account
   */
  void update(Account account);

  /**
   * delete
   * @param id
   */
  void del(int id);
}

Implementation class:

@Service("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {
  @Autowired
  @Qualifier("accountDao")
  private IAccountDao dao = null;

  @Override
  public List<Account> findAll() {
    return dao.findAll();
  }

  @Override
  public Account findOne(int id) {
    return dao.findOne(id);
  }

  @Override
  public void save(Account account) {
    dao.save(account);
  }

  @Override
  public void update(Account account) {
    dao.update(account);
  }

  @Override
  public void del(int id) {
    dao.del(id);
  }
}

2.3.7 - create jdbc configuration file

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1
jdbc.user=root
jdbc.password=root

2.3.8 - create jdbc configuration class

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
  @Value("${jdbc.driver}")
  private String driver;
  @Value("${jdbc.url}")
  private String url;
  @Value("${jdbc.user}")
  private String username;
  @Value("${jdbc.password}")
  private String password;

  /**
   * Create QueryRunner object
   * @param ds
   * @return
   */
  @Bean(name="runner")
  @Scope("prototype")
  public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource ds){
    return new QueryRunner(ds);
  }

  /**
   * Create data source object
   * @return
   */
  @Bean(name = "dataSource")
  public DataSource createDataSource() {
    try {
      ComboPooledDataSource ds = new ComboPooledDataSource();
      ds.setUser(username);
      ds.setPassword(password);
      ds.setDriverClass(driver);
      ds.setJdbcUrl(url);
      return ds;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

2.3.9 - create Spring configuration class

/**
    [Configuration]
        Identify that the class is a configuration class, equivalent to the bean.xml configuration file
    [ComponentScan]
        Specifies the package that spring will scan when it initializes the container. And in spring's xml configuration file:
        <context:component-scan base-package="cn.lpl666"/>It's the same.
 */
@Configuration    // Indicates that the class is a configuration class
@ComponentScan("cn.lpl666")  // Packages to be scanned by spring container
@Import({JdbcConfig.class})  // Import other configuration classes
public class SpringConfiguration {
}

2.3.10 - Testing

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class ClientTest {
  // Create Spring container
  private ApplicationContext as = null;
  // Get IAccountServices object
  @Autowired
  private IAccountServices services = null;
  /**
   * Query all
   */
  @Test
  public void findAll(){
    List<Account> all = services.findAll();
    System.out.println(all);
  }

  /**
   * Query one
   */
  @Test
  public void findOne(){
    Account account = services.findOne(1);
    System.out.println(account);
  }

  /**
   * Add to
   */
  @Test
  public void save(){
    Account account = new Account();
    account.setName("test3");
    account.setMoney(40000);
    services.save(account);
  }

  /**
   * modify
   */
  @Test
  public void update(){
    Account account = services.findOne(1);
    account.setMoney(9999);
    services.update(account);
  }

  /**
   * delete
   */
  @Test
  public void del(){
    services.del(6);
  }

}

2.4 - new notes

@Configuration

Function: used to specify that the current class is a spring Configuration class from which annotations will be loaded when a container is created. Annotationapplicationcontext (class with @ Configuration annotation) is required to get the container.

Property: value: bytecode used to specify configuration class

@Configuration    // Indicates that the class is a configuration class
public class SpringConfiguration {
}

@ComponentScan

Function: used to specify the package that spring will scan when initializing the container. The function is the same as that of < context: component scan base package = "CN. Lpl666" / > in spring's xml configuration file.

Property: basePackages: used to specify the packages to scan. This is the same as the value attribute in this annotation.

@Configuration    // Indicates that the class is a configuration class
@ComponentScan("cn.lpl666")  // Packages to be scanned by spring container
public class SpringConfiguration {
}

@Bean

Function: the annotation can only be written on the method, indicating that an object is created with this method and put into the spring container.

Attribute: Name: specifies a name (that is, the bean id) for the object created by the current @ bean annotation method.

public class JdbcConfig {
  /**
   * Create QueryRunner object
   * @param ds
   * @return
   */
  @Bean(name="runner")
  @Scope("prototype")
  public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource ds){
    return new QueryRunner(ds);
  }
  
}

@PropertySource

Role: used to load the configuration in the. Properties file. For example, when we configure the data source, we can write the information of the connection database to the properties configuration file, and then use this annotation to specify the location of the properties configuration file.

Property: value []: used to specify the property file location. If it is under classpath, you need to write classpath:

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {}

@Import

Function: used to import other Configuration classes. When importing other Configuration classes, you can no longer write @ Configuration annotation. Of course, there's no problem with that. Property: value []: used to specify the bytecode of other Configuration classes.

@Import({JdbcConfig.class})  // Import other configuration classes
public class SpringConfiguration {
}

2.5 - get container by annotation

ApplicationContext ac =
new AnnotationConfigApplicationContext(SpringConfiguration.class);

2.6- Spring integrates Junit

2.6.1- problem

In the test class, each test method has the following two lines of code:

ApplicationContext ac =new AnnotationConfigApplicationContext(SpringConfiguration.class);

IAccountService as = ac.getBean("accountService",IAccountService.class); 

The purpose of these two lines of code is to get the container. If it is not written, it will directly prompt for null pointer exception. So it can't be deleted easily.

2.6.2 - solution analysis

To solve the above problems, we need the program to help us create containers automatically. Once the program can automatically create a spring container for us, we will
 No need to create it manually, and the problem will be solved.
junit can't be implemented because it doesn't know whether we use the spring framework, let alone help us create the spring container. Fortunately, junit exposes a comment that allows us to replace its runner.
At this point, we need to rely on the spring framework because it provides a runner that can read configuration files (or annotations) to create containers. I
 You just need to tell it where the configuration file is.

2.6.3 - configuration steps

Step 1: Maven introduces dependency package, and junit version is 4.12 or above

    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--spring-test-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.3.RELEASE</version>
      <scope>test</scope>
    </dependency>

Step 2: replace the original runner with the @ RunWith annotation

@RunWith(SpringJUnit4ClassRunner.class)
public class ClientTest {}

Step 3: use @ ContextConfiguration to specify the location of the spring configuration file

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
// For xml configuration, use: @ ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {}

@ContextConfiguration annotation:

  • locations property: used to specify the location of the configuration file.
  • If it is under the classpath, you need to use classpath: to indicate the classes attribute: to specify the annotation class. When you do not use xml configuration, you need to use this property to specify the location of the annotation class

Step 4: use @ Autowired to inject data into variables in the test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {
  // Get IAccountServices object
  @Autowired
  private IAccountServices services = null;
}

2.6.4 - why not configure the test class into xml

Before explaining this problem, let's clear up our doubts. Can we use it in XML?
The answer is yes, no problem, it can be used.
So why not configure to xml?
This is because:
    First: when we configure a bean in xml and spring loads the configuration file to create the container, the object is created.
    Second: the test class is only used when we test the function, and it does not participate in the program logic in the project, nor solve the requirements
    Question, so created, not used. Then the existence of the container will cause waste of resources.
    Therefore, based on the above two points, we should not configure the test into an xml file.

Posted by davinci on Thu, 13 Feb 2020 23:29:13 -0800