Spring Cloud微服务实战---1.4.JPA与JDBC协同

11 篇文章 1 订阅
11 篇文章 0 订阅

我们知道,如果采用领域驱动开发(DDD)的话,采用JPA技术,会非常方便。但是对于复杂的多表联合查询,使用JPA技术就比较费力了。为了解决复杂SQL查询问题,很多项目采用了MyBatis。但是Spring提倡大家使用JPA,对MyBatis技术实际上是有一点儿抵制的。我们在实际项目中,采用数据库增删改采用JPA,而复杂数据库SQL查询,直接采用JDBC来实现。采用这种方式,也符合大容量、高并发网站架构,因为在处理大容量、高并发时,数据库读写分离是普遍采用技术。我们采用JPA与JDBC相结合的方式,也可以完美地用于数据库读写分离场景。
我们首先需要向工程中添加依赖库:

......
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-configuration-processor</artifactId>
		<optional>true</optional>
	</dependency>

	<dependency>
		<groupId>com.zaxxer</groupId>
		<artifactId>HikariCP</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-thymeleaf</artifactId>
	</dependency>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jdbc</artifactId>
	</dependency>
......	

接着我们在src/main/resource/application.properties中声明项目中需要用到的数据源,我们采用Spring Boot内置的数据库连接池,采用读、写分离原则,分别定义read-ds和write-ds,如下所示:


# JdbcTemplate
spring.datasource.read-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.read-ds.jdbc-url=jdbc:mysql://localhost:3306/MseDb?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.read-ds.username=mse
spring.datasource.read-ds.password=mse2018
spring.datasource.read-ds.max-active=40
spring.datasource.read-ds.max-idle=5
spring.datasource.read-ds.min-idle=5
spring.datasource.read-ds.initial-size=5
spring.datasource.read-ds.type=com.zaxxer.hikari.HikariDataSource

spring.datasource.write-ds.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.write-ds.jdbc-url=jdbc:mysql://localhost:3306/MseDb?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&characterEncoding=utf-8
spring.datasource.write-ds.username=mse
spring.datasource.write-ds.password=mse2018
spring.datasource.write-ds.max-active=10
spring.datasource.write-ds.max-idle=5
spring.datasource.write-ds.min-idle=5
spring.datasource.write-ds.initial-size=5
spring.datasource.write-ds.type=com.zaxxer.hikari.HikariDataSource

接着我们在程序中对数据库进行配置,如下所示:

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;

@Configuration
public class MseDatasource {
	@Bean(name = "readDataSource")
	@Qualifier("readDataSource")
	@ConfigurationProperties(prefix = "spring.datasource.read-ds")
	public DataSource readDataSource(){
	    return DataSourceBuilder.create().build();
	}
	
	@Bean(name = "writeDataSource")
	@Qualifier("writeDataSource")
	@Primary
	@ConfigurationProperties(prefix = "spring.datasource.write-ds")
	public DataSource secondaryDataSource(){
	    return DataSourceBuilder.create().build();
	}
	
	@Bean(name = "readJdbcTemplate")
	public JdbcTemplate readJdbcTemplate(@Qualifier("readDataSource")DataSource readDataSource){
	    return new JdbcTemplate(readDataSource);
	}
	
	@Bean(name = "writeJdbcTemplate")
	public JdbcTemplate writeJdbcTemplate(@Qualifier("writeDataSource")DataSource writeDataSource){
	    return new JdbcTemplate(writeDataSource);
	}
}

我们在需要进行数据库操作的DAO文件中,引入JdbcTemplate对象,进行数据库操作,如下所示:

import java.util.List;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

@Service
public class ProductMysqlDao implements ProductDao {	
	@Autowired
    @Qualifier("readJdbcTemplate")
    private JdbcTemplate jdbcTemplate;
	
	public List<ProductVo> getProducts() {
		Object[] params = new Object[1];
		params[0] = 1;
		List<ProductVo> recs = jdbcTemplate.query("select product_name, image_url, price from t_product where product_category_id=?", params, (rs, num) -> {
			ProductVo vo = new ProductVo();
			return vo;
		});
		return recs;
	}	
}

在服务的调用处,引入DAO对象调用其方法,如下所示:

// ProductController.java
	@Autowired
	private ProductMysqlDao dao;
	
	@GetMapping("/products/jdbc")
	public List<ProductVo> testJdbc() {
		//ProductDao dao = DaoFactory.getProductDao(DaoFactory.DB_MYSQL);
		return dao.getProducts();
	}

到此为止,我们就成功配置出了能够满足读写分离需求的数据库连接。我们可以将增删改查操作委托给JPA,而复杂的多表查询用JdbcTemplate来实现。
在实际应用中,由于需要保证数据的一致性,因此事务是非常重要的。我们在设计时,尽量要让事务封装在一个微服务之内,如果是跨微服务的事务,由于是分布式事务,处理起来非常复杂,我们一般应该尽量避免。
我们首先在ProductMysqlDao中生成一个需要插入两个产品的方法,并将该方法注解为需要事务,如下所示:

	@Transactional
	public HttpSimpleResponse testTransaction() {
		HttpSimpleResponse resp = new HttpSimpleResponse(0, "Ok", "Success");
		Object[] args = new Object[5];
		args[0] = "新1001";
		args[1] = "x1001.jpg";
		args[2] = 1333.3;
		args[3] = 3;
		args[4] = 1;
		int[] argTypes = new int[5];
		argTypes[0] = Types.VARCHAR;
		argTypes[1] = Types.VARCHAR;
		argTypes[2] = Types.DOUBLE;
		argTypes[3] = Types.INTEGER;
		argTypes[4] = Types.INTEGER;
		int affectedRows = writeJdbcTemplate.update("insert into t_product(product_name, image_url, price, quantity, product_category_id) values(?, ?, ?, ?, ?)", args, argTypes);
		System.out.println("ar=" + affectedRows + "!");	
		args[0] = "事务001";
		int r2 = writeJdbcTemplate.update("insert into t_product(product_name, image_url, price, quantity, product_category_id) values(?, ?, ?, ?, ?)", args, argTypes);
		System.out.println("r2=" + r2 + "!");		
		return resp;
	}

我们用@Transactional来表明需要使用事务,其后是两条数据库插入操作。
我们在服务定义类ProductController中,调用此方法:

	@GetMapping("/products/testTransaction")
	public HttpSimpleResponse testTransaction() {
		try {
			return dao.testTransaction();
		} catch (Exception ex) {
			return new HttpSimpleResponse(4, "事务失败", ex.getMessage());
		}
	}

如果发生任何失败情况,直接返回失败原因。
如果我们直运行上面的代码,会成功向数据库中插入两条产品记录。我们将第2个SQL语句改错,则会提示如下信息:
在这里插入图片描述
如上图所示,出现了数据操作错误,并且虽然第一条可以正常插入,但是整个事务的状态是失败,所以数据库中也没有插入任何记录。由此可见,只需要一个简单的@Transactional注解,就可以实现对事务的支持,非常强大。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值