Руководство по Spring. Управление транзакциями – декларативный метод (пример приложения).

Декларативный метод управления транзакциями позволяет нам управлять транзакциями с помощью конфигурационного файла XML, либо с помощью аннотаций. Другими словами, мы отделяем наш модуль управления транзакциями от бизнес-логики приложения.

Для понимания, что такоe, декларативный метод управления транзакциями на практике рассмотрим пример небольшого приложения.

Пример приложения:

Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.

Создадим в нашей базе данных (далее – БД) две таблицы.

Таблица DEVELOPERS


CREATE TABLE DEVELOPERS(
   ID   INT NOT NULL AUTO_INCREMENT,
   NAME VARCHAR(50) NOT NULL,
   SPECIALTY VARCHAR(50) NOT NULL,
   EXPERIENCE INT NOT NULL,
   PRIMARY KEY (ID)
);

Таблица PROJECTS


CREATE TABLE PROJECTS(
   DEVELOPERS_ID INT NOT NULL,
   NAME  VARCHAR(50) NOT NULL,
   COMPANY VARCHAR(50) NOT NULL
);

Первая таблица содержит данные о разработчиках, а вторая – о проектах, в которых участвовал разработчик.

Структура проекта
transactionsDeclarativeStructure

Developer


package net.proselyte.transactionsDeclarative.model;

import java.util.List;

public class Developer {
    private Integer id;
    private String name;
    private String specialty;
    private List projects;
    private Integer experience;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public List getProjects() {
        return projects;
    }

    public void setProjects(List projects) {
        this.projects = projects;
    }

    public Integer getExperience() {
        return experience;
    }

    public void setExperience(Integer experience) {
        this.experience = experience;
    }

    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nName: " + name +
                "\nSpecialty: " + specialty +
                "\nExperience: " + experience + "\n";
    }
}

Project


package net.proselyte.transactionsDeclarative.model;

public class Project {
    private Integer developersId;
    private String projectName;
    private String companyName;

    public Integer getDevelopersId() {
        return developersId;
    }

    public void setDevelopersId(Integer developersId) {
        this.developersId = developersId;
    }

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @Override
    public String toString() {
        return "Project:" +
                "\nDeveloper's Id: " + developersId +
                "\nProject name: " + projectName +
                "\nCompanyName: " + companyName + "\n";
    }
}


DeveloperDao


package net.proselyte.transactionsDeclarative.dao;


import net.proselyte.transactionsDeclarative.model.Developer;
import net.proselyte.transactionsDeclarative.model.Project;

import javax.sql.DataSource;
import java.util.List;

public interface DeveloperDao {
    public void setDataSource(DataSource dataSource);

    public void createDeveloper(String name, String specialty, Integer experience);

    public List listDevelopers();

    public List listDevelopersProjects(Integer id);
}


ProjectDao


package net.proselyte.transactionsDeclarative.dao;

import javax.sql.DataSource;

public interface ProjectDao {
    public void setDataSource(DataSource dataSource);

    public void createProject(Integer developersId, String projectName, String companyName);
}


JdbcTemplateDeveloperDaoImpl


package net.proselyte.transactionsDeclarative.dao.jdbcTemplate;

import net.proselyte.transactionsDeclarative.dao.DeveloperDao;
import net.proselyte.transactionsDeclarative.model.Developer;
import net.proselyte.transactionsDeclarative.model.Project;
import net.proselyte.transactionsDeclarative.utils.DevelopersMapper;
import net.proselyte.transactionsDeclarative.utils.ProjectsMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.List;

public class JdbcTemplateDeveloperDaoImpl implements DeveloperDao {
    private JdbcTemplate jdbcTemplate;

    @Override
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }


    @Override
    public void createDeveloper(String name, String specialty, Integer experience) {


        String SQL = "INSERT INTO DEVELOPERS (DEVELOPER_NAME, SPECIALTY, EXPERIENCE) VALUES (?,?,?)";
        jdbcTemplate.update(SQL, name, specialty, experience);
        System.out.println("Developer's record created/updated successfully. Name: " +
                name + ", Specilaty: " + specialty + ", Experience: " + experience + "\n");
    }

    @Override
    public List listDevelopers() {
        String SQL = "SELECT * FROM DEVELOPERS";
        List developers = jdbcTemplate.query(SQL, new DevelopersMapper());

        return developers;
    }

    @Override
    public List listDevelopersProjects(Integer id) {
        String SQL = "SELECT * FROM PROSELYTE_TUTORIAL.PROJECTS WHERE DEVELOPERS_ID = " + id;
        List projectList = jdbcTemplate.query(SQL, new ProjectsMapper());
        return projectList;
    }
}

JdbcTemplateProjectDaoImpl


package net.proselyte.transactionsDeclarative.dao.jdbcTemplate;

import net.proselyte.transactionsDeclarative.dao.ProjectDao;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

public class JdbcTemplateProjectDaoImpl implements ProjectDao {

    private JdbcTemplate jdbcTemplate;

    @Override
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Override
    public void createProject(Integer developerId, String projectName, String companyName) {
        String SQL = "INSERT INTO PROJECTS (DEVELOPERS_ID, NAME, COMPANY) VALUES (?,?,?)";
        jdbcTemplate.update(SQL, developerId, projectName, companyName);
        System.out.println("Project record created successfully. Project name: " +
                projectName + ", Company: " + companyName + "\n");
    }
}

DevelopersMapper


package net.proselyte.transactionsDeclarative.utils;

import net.proselyte.transactionsDeclarative.model.Developer;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class DevelopersMapper implements RowMapper {
    @Override
    public Developer mapRow(ResultSet rs, int rowNum) throws SQLException {
        Developer developer = new Developer();

        developer.setId(rs.getInt("id"));
        developer.setName(rs.getString("developer_name"));
        developer.setSpecialty(rs.getString("specialty"));
        developer.setExperience(rs.getInt("experience"));

        return developer;
    }
}


ProjectsMapper


package net.proselyte.transactionsDeclarative.utils;

import net.proselyte.transactionsDeclarative.model.Project;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

public class ProjectsMapper implements RowMapper {
    @Override
    public Project mapRow(ResultSet rs, int rowNum) throws SQLException {
        Project project = new Project();

        project.setDevelopersId(rs.getInt("DEVELOPERS_ID"));
        project.setProjectName(rs.getString("NAME"));
        project.setCompanyName(rs.getString("COMPANY"));

        return project;
    }
}


Конфигурационный файл transaction-declarative-config.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:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/aop
   http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

    <!-- Initialization for data source -->
    <bean id="dataSource"
          class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/ИМЯ_ВАШЕЙ_БАЗЫ_ДАННЫХ"/>
        <property name="username" value="ВАШЕ_ИМЯ_ПОЛЬЗОВАТЕЛЯ"/>
        <property name="password" value="ВАШ_ПАРОЛЬ"/>
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="create"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="createOperation"
                      expression="execution(* net.proselyte.transactionsDeclarative.dao.jdbcTemplate.JdbcTemplateDeveloperDaoImpl.create(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="createOperation"/>
    </aop:config>

    <!-- Initialization for TransactionManager -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Definition for developerJDBCTemplate bean -->
    <bean id="developerJDBCTemplate"
          class="net.proselyte.transactionsDeclarative.dao.jdbcTemplate.JdbcTemplateDeveloperDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- Definition for projectJDBCTemplate bean -->
    <bean id="projectJDBCTemplate"
          class="net.proselyte.transactionsDeclarative.dao.jdbcTemplate.JdbcTemplateProjectDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

ApplicationRunner


package net.proselyte.transactionsDeclarative;


import net.proselyte.transactionsDeclarative.dao.jdbcTemplate.JdbcTemplateDeveloperDaoImpl;
import net.proselyte.transactionsDeclarative.dao.jdbcTemplate.JdbcTemplateProjectDaoImpl;
import net.proselyte.transactionsDeclarative.model.Developer;
import net.proselyte.transactionsDeclarative.model.Project;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class ApplicationRunner {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("transaction-declarative-config.xml");
        JdbcTemplateDeveloperDaoImpl developerDao =
                (JdbcTemplateDeveloperDaoImpl) context.getBean("developerJDBCTemplate");
        JdbcTemplateProjectDaoImpl projectDao =
                (JdbcTemplateProjectDaoImpl) context.getBean("projectJDBCTemplate");

        System.out.println("======== Creating project's records ========");
        projectDao.createProject(90, "ProselyteTutorial", "Proselyte.net");
        projectDao.createProject(90, "SkybleLib", "SkybleSoft");

        System.out.println("======== Creating developer's records ========");
        developerDao.createDeveloper("Proselyte", "Java Developer", 3);
        developerDao.createDeveloper("Mike", "C++ Developer", 5);

        System.out.println("======== List of Developers ========");
        List developerList = developerDao.listDevelopers();
        for (Developer developer : developerList) {
            System.out.println(developer);
        }

        System.out.println("======== Proselyte Developer's Projects ========");
        List projects = developerDao.listDevelopersProjects(90);
        for (Project project : projects) {
            System.out.println(project);
        }

    }
}

Результат работы программы


/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7534 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-jdbc/4.1.1.RELEASE/spring-jdbc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.1.1.RELEASE/spring-tx-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/aspectj/aspectjtools/1.8.8/aspectjtools-1.8.8.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.transactionsDeclarative.ApplicationRunner
Feb 09, 2016 3:25:00 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@246b179d: startup date [Tue Feb 09 15:25:00 EET 2016]; root of context hierarchy
Feb 09, 2016 3:25:00 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [transaction-declarative-config.xml]
Feb 09, 2016 3:25:01 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
======== Creating project's records ========
Tue Feb 09 15:25:01 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Project record created successfully. Project name: ProselyteTutorial, Company: Proselyte.net

Tue Feb 09 15:25:02 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Project record created successfully. Project name: SkybleLib, Company: SkybleSoft

======== Creating developer's records ========
Tue Feb 09 15:25:02 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Tue Feb 09 15:25:02 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Developer's record created/updated successfully. Name: Proselyte, Specilaty: Java Developer, Experience: 3

Developer's record created/updated successfully. Name: Mike, Specilaty: C++ Developer, Experience: 5

======== List of Developers ========
Tue Feb 09 15:25:02 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Developer:
id: 90
Name: Proselyte
Specialty: Java Developer
Experience: 3

Developer:
id: 91
Name: Mike
Specialty: C++ Developer
Experience: 5

======== Proselyte Developer's Projects ========
Tue Feb 09 15:25:02 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Project:
Developer's Id: 90
Project name: ProselyteTutorial
CompanyName: Proselyte.net

Project:
Developer's Id: 90
Project name: SkybleLib
CompanyName: SkybleSoft


В этом примере мы рассмотрели основы управления транзакциями в Spring декларативным методом.