JDBC(Java DataBase Connectivity)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序,
术语:什么是持久层:持久层就是指对数据进行持久化操作的代码,比如将数据保存到数据库、文件、磁盘等操作都是持久层操作。所谓持久就是保存起来的意思。对于web应用最常用的持久层框架就是JDBC、Mybatis、JPA。
直接在 Java 程序中使用 JDBC 比较复杂,需要 7 步才能完成数据库的操作:
关键代码如下:
try {
// 1、加载数据库驱动
Class.forName(driver);
// 2、获取数据库连接
conn = DriverManager.getConnection(url, username, password);
// 3、获取数据库操作对象
stmt = conn.createStatement();
// 4、定义操作的 SQL 语句
String sql = "select * from user where id = 6";
// 5、执行数据库操作
rs = stmt.executeQuery(sql);
// 6、获取并操作结果集
while (rs.next()) {
// 解析结果集
}
} catch (Exception e) {
// 日志信息
} finally {
// 7、关闭资源
}
通过上面的示例可以看出直接使用 JDBC 来操作数据库比较复杂。为此,Spring Boot 针对 JDBC 的使用提供了对应的 Starter 包:spring-boot-starter-jdbc,它其实就是在 Spring JDBC 上做了进一步的封装,方便在 Spring Boot 生态中更好的使用 JDBC,下面进行示例演示。
不论是JDBC,还是封装之后的Spring JDBC,直接操作数据库都比较麻烦。如果企业有成熟的ORM知识积累,并且无特殊需求,不建议直接使用JDBC操作数据库
第一步:引入maven依赖包,包括spring JDBC和MySQL驱动。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
第二步:修改application.yml,增加数据库连接、用户名、密码相关的配置。driver-class-name请根据自己使用的数据库和数据库版本准确填写。
spring:
datasource:
url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: dhy
password: 12346
driver-class-name: com.mysql.cj.jdbc.Driver
spring jdbc集成完毕之后,我们来写代码做一个基本的测试。首先我们新建一张测试表article
CREATE TABLE `article` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`author` VARCHAR(32) NOT NULL,
`title` VARCHAR(32) NOT NULL,
`content` VARCHAR(512) NOT NULL,
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
)
COMMENT='文章'
ENGINE=InnoDB;
MySQL5.6之后的版本才支持CURRENT_TIMESTAMP
mysql中对于UPDATE_TIME字段我们有时候会设置ON UPDATE CURRENT_TIMESTAMP,表示在数据库数据有更新的时候UPDATE_TIME的时间会自动更新
DAO层代码:
满足驼峰标识也可以自动映射
。如:数据库create_time字段映射到createTime属性。@Repository //持久层依赖注入注解
public class ArticleJDBCDAO {
@Resource
private JdbcTemplate jdbcTemplate;
//保存文章
public void save(Article article) {
//jdbcTemplate.update适合于insert 、update和delete操作;
jdbcTemplate.update("INSERT INTO article(author, title,content) values(?, ?, ?)",
article.getAuthor(),
article.getTitle(),
article.getContent());
}
//删除文章
public void deleteById(Long id) {
//jdbcTemplate.update适合于insert 、update和delete操作;
jdbcTemplate.update("DELETE FROM article WHERE id = ?",id);
}
//更新文章
public void updateById(Article article) {
//jdbcTemplate.update适合于insert 、update和delete操作;
jdbcTemplate.update("UPDATE article SET author = ?, title = ? ,content = ? WHERE id = ?",
article.getAuthor(),
article.getTitle(),
article.getContent(),
article.getId());
}
//根据id查找文章
public Article findById(Long id) {
//queryForObject用于查询单条记录返回结果
return (Article) jdbcTemplate.queryForObject("SELECT * FROM article WHERE id=?",
new Object[]{id},new BeanPropertyRowMapper<>(Article.class));
}
//查询所有
public List<Article> findAll(){
//query用于查询结果列表
return (List<Article>) jdbcTemplate.query("SELECT * FROM article ", new BeanPropertyRowMapper<>(Article.class));
}
}
service层接口
public interface ArticleService {
void saveArticle(Article article);
void deleteArticle(Long id);
void updateArticle(Article article);
Article getArticle(Long id);
List<Article> getAll();
}
service层操作JDBC持久层
@Slf4j
@Service //服务层依赖注入注解
public class ArticlleJDBCService implements ArticleService {
@Resource
private
ArticleJDBCDAO articleJDBCDAO;
@Transactional
public void saveArticle( Article article) {
articleJDBCDAO.save(article);
//int a = 2/0; //人为制造一个异常,用于测试事务
}
public void deleteArticle(Long id){
articleJDBCDAO.deleteById(id);
}
public void updateArticle(Article article){
articleJDBCDAO.updateById(article);
}
public Article getArticle(Long id){
return articleJDBCDAO.findById(id);
}
public List<Article> getAll(){
return articleJDBCDAO.findAll();
}
}
最后,在我们之前的章节为大家实现的ArticleController中调用ArticleRestJDBCService 实现方法,进行从Controller 到 Service 到 DAO层的全流程测试。
@RestController
public class AtricleController
{
@Autowired
private ArticleService articleService;
@PostMapping("/articles")
public Object save(@RequestBody Article article)
{
articleService.saveArticle(article);
return article;
}
@PutMapping("/articles")
public Object update(@RequestBody Article article)
{
articleService.updateArticle(article);
return article;
}
@GetMapping("/articles")
public Object get(@RequestParam Long id)
{
Article article = articleService.getArticle(id);
return article;
}
@DeleteMapping("/articles")
public Object delete(@RequestParam Long id)
{
articleService.deleteArticle(id);
return id;
}
}
随着应用的数据量增多,很可能会采用数据分库存储的方案,所以说对于我们的持久层代码可能面临在一个服务函数中操作多个数据库的场景。我们该如何通过配置去满足这样的场景?本节来为大家介绍。
application.yml配置2个数据源,第一个叫做primary,第二个叫做secondary。注意两个数据源连接的是不同的库,testdb和testdb2.
spring:
datasource:
primary:
jdbc-url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: test
password: 4rfv$RFV
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://192.168.161.3:3306/testdb2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: test
password: 4rfv$RFV
driver-class-name: com.mysql.cj.jdbc.Driver
@Configuration
public class DataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.primary") //testdb
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix="spring.datasource.secondary") //testdb2
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name="primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate (@Qualifier("primaryDataSource") DataSource dataSource ) {
return new JdbcTemplate(dataSource);
}
@Bean(name="secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(@Qualifier("secondaryDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
Spring实现多数据源配置的思想和使用方式
@Resource
private JdbcTemplate primaryJdbcTemplate;
//以保存文章为例,新增一个参数:jdbcTemplate ,其他的方法照做
public void save(Article article,JdbcTemplate jdbcTemplate ) {
if(jdbcTemplate == null){ //判断新增参数不能为空,如果为空使用primaryJdbcTemplate
jdbcTemplate= primaryJdbcTemplate;
}
//jdbcTemplate.update适合于insert 、update和delete操作;
jdbcTemplate.update("INSERT INTO article(author, title,content) values(?, ?, ?)",
article.getAuthor(),
article.getTitle(),
article.getContent());
}
在src/test/java目录下,加入如下单元测试类,并进行测试。正常情况下,在testdb和testdb2数据库的article表,将分别插入一条数据,表示多数据源测试成功
// @RunWith(SpringRunner.class) Junit4
@ExtendWith(SpringExtension.class) //Junit5
@SpringBootTest
public class SpringJdbcTest {
@Resource
private ArticleJDBCDAO articleJDBCDAO;
@Resource
private JdbcTemplate primaryJdbcTemplate;
@Resource
private JdbcTemplate secondaryJdbcTemplate;
@Test
public void testJdbc() {
articleJDBCDAO.save(
Article.builder()
.author("zimug").title("primaryJdbcTemplate").content("ceshi").createTime(new Date())
.build(),
primaryJdbcTemplate);
articleJDBCDAO.save(
Article.builder()
.author("zimug").title("secondaryJdbcTemplate").content("ceshi").createTime(new Date())
.build(),
secondaryJdbcTemplate);
}
}
在上一节代码的的Service层做一下测试,人为制造一个被除数为0的异常。然后对该服务对应的Controller方法发送请求。(postman)
@Resource
private JdbcTemplate primaryJdbcTemplate;
@Resource
private JdbcTemplate secondaryJdbcTemplate;
@Transactional //表示两个数据库操作放在一个事务里面执行
public Article saveArticle( Article article) {
articleJDBCDAO.save(article,primaryJdbcTemplate);
articleJDBCDAO.save(article,secondaryJdbcTemplate);
int a = 2/0; //人为制造一个被除数为0的异常
return article;
}
secondaryJdbcTemplate的数据插入数据成功,primaryJdbcTemplate的数据插入数据失败,这显然不是我们想要的结果。这是因为:数据库事务不能跨连接, 当然也就不能跨数据源,更不能跨库。一旦出现跨连接的情况,也就成了分布式事务,分布式事务不能单纯依赖于数据库去处理。
我们期望的事务效果是:正常情况下数据库操作都成功,如果出现异常,操作必须都回滚、都失败。A向B进行银行转账,你不能A账户的钱少了,B账户的钱没增加。
大家要注意数据库事务和分布式事务区别:数据库事务是由单一的数据库实例来控制事务的提交与回滚。而分布式事务至少涉及到两个数据库实例,不能单一的由某一方自己控制事务的提交与回滚。
我们这一节的实现方式,是通过JTA来实现“分布式事务”。
举个例子:你自驾游想几点出发就几点出发,想几点回来就几点回来;如果你是跟团游,就得听导游的安排,不然你就赶不上车。JTA就是跟团游的团长、导游,负责管理团体的集合与返回。
大家先不用忙着去理解JTA这个名词,我们先实现分布式事务。用最简单的话说,分布式事务就是跨数据库操作的事务。一个事务的多数据库操作,要么都成功,要么都失败回滚。后面我们专门做一个章节讲解事务与分布式事务。
atomikos是对JTA规范的一个标准实现。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
双数据源配置testdb和testdb2。删掉原有数据库连接配置.
primarydb:
uniqueResourceName: primary
xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
xaProperties:
url: jdbc:mysql://192.168.161.3:3306/testdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
user: test
password: 4rfv$RFV
exclusiveConnectionMode: true
minPoolSize: 3
maxPoolSize: 10
testQuery: SELECT 1 from dual #由于采用HikiriCP,用于检测数据库连接是否存活。
secondarydb:
uniqueResourceName: secondary
xaDataSourceClassName: com.mysql.cj.jdbc.MysqlXADataSource
xaProperties:
url: jdbc:mysql://192.168.161.3:3306/testdb2?useUnicode=true&characterEncoding=utf-8&useSSL=false
user: test
password: 4rfv$RFV
exclusiveConnectionMode: true
minPoolSize: 3
maxPoolSize: 10
testQuery: SELECT 1 from dual #由于采用HikiriCP,用于检测数据库连接是否存活。
MysqlXADataSource的解释:根据jdbc 4.0规范(12.2):XA数据源生成能够在全局/分布式事务中使用的XA连接。如果需要跨多个数据库或JMS调用的事务,则可能需要此类连接。您可以在此处找到有关概念的明确说明:http://www.theserverside.com/discussions/thread.tss?thread_id=21385#95346
如果不使用分布式事务,则在声明驱动程序时无需指定xa-datasource-class。这个xa-datasource-class是专门为分布式事务准备的
下面是数据源bean的配置,将上面配置文件中的属性加载到Spring Bean里面。也就是将配置参数应用到我们的双数据库数据源实例对象中。
@Configuration
public class DataSourceConfig {
//jta数据源primarydb
@Bean(initMethod="init", destroyMethod="close", name="primaryDataSource")
@Primary
@ConfigurationProperties(prefix = "primarydb")
public DataSource primaryDataSource() {
//这里是关键,返回的是AtomikosDataSourceBean,所有的配置属性也都是注入到这个类里面
return new AtomikosDataSourceBean();
}
//jta数据源secondarydb
@Bean(initMethod="init", destroyMethod="close", name="secondaryDataSource")
@ConfigurationProperties(prefix = "secondarydb")
public DataSource secondaryDataSource() {
return new AtomikosDataSourceBean();
}
//primaryJdbcTemplate使用primaryDataSource数据源
@Bean
public JdbcTemplate primaryJdbcTemplate(
@Qualifier("primaryDataSource") DataSource primaryDataSource) {
return new JdbcTemplate(primaryDataSource);
}
//secondaryJdbcTemplate使用secondaryDataSource数据源
@Bean
public JdbcTemplate secondaryJdbcTemplate(
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
return new JdbcTemplate(secondaryDataSource);
}
}
事务管理器负责协调多个JTA数据源实现事务机制。固定写法,不用纠结UserTransaction 、TransactionManager 、JtaTransactionManager 都是什么。这个东西它不是业务逻辑,它就是一个配置。你问我为什么这么配置,我会告诉你:这是乌龟的屁股—规定。为什么女人能生孩子,男人不行,你问我我也不知道,就是这么安排的!谁这么安排的?女娲(JTA规范)安排的。
@Configuration
public class TransactionManagerConfig {
@Bean
public UserTransaction userTransaction() throws SystemException {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
JtaTransactionManager manager = new JtaTransactionManager(userTransaction, atomikosTransactionManager());
return manager;
}
}
这时候,再测试一下文首中的测试用例:人为制造一个被除数为0的异常,异常抛出,两个数据库实例中的article表将都无法插入数据。符合事务的要求:正常情况数据操作都成功,异常情况数据操作都失败回滚。
优点: 能够支持分布式事务
缺点: 性能开销大,不适合用于高并发场景
目前java 持久层ORM框架应用最广泛的就是JPA和Mybatis。JPA只是一个ORM框架的规范, 对该规范的实现比较完整就是Spring Data JPA(底层基于Hibernate实现),是基于Spring的数据持久层框架,也就是说它只能用在Spring环境内。Mybatis也是一个优秀的数据持久层框架,能比较好的支持ORM实体关系映射、动态SQL等。
笔者在学习这两个框架的过程中,看过不少的帖子,每当有帖子比较这两个框架的优缺点,就引来一场论战。从笔者的角度,为什么国内的开发人员或者开发团队较少使用JPA?为了避免有人抨击我,我特意去做了一下国内某度指数搜索,这个数据骗不了人。
图中蓝色线条为Mybatis搜索量,绿色为JPA搜索量。如果你换一个国外的搜索指数,你会得到一个完全不同的结果。那么这是为什么呢?我们还要从JPA的特点说起:
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
JPAQuery<Tuple> jpaQuery = queryFactory.select(QTCity.tCity,QTHotel.tHotel)
.from(QTCity.tCity)
.leftJoin(QTHotel.tHotel)
.on(QTHotel.tHotel.city.longValue().eq(QTCity.tCity.id.longValue()));
//添加查询条件
jpaQuery.where(predicate);
//拿到结果
return jpaQuery.fetch();
另一种方法是使用NativeQuery,我仍然想问:你希望在java代码里面用拼字符串的方式写SQL么?
@Entity
@NamedNativeQueries(value={
@NamedNativeQuery(
name = "studentInfoById",
query = " SELECT * FROM student_info "
+ " WHERE stu_id = ? ",
resultClass = Student.class
)
})
@Table(name="student_info")
以上的这部分实现还没有考虑到动态SQL的问题,如果考虑到动态SQL,写法会更复杂。所谓的动态SQL就是:根据传入参数条件的不同,构造不同的SQL
,很多的比较这两个框架的文章都忽略了动态SQL的问题,这方面Mybatis支持的更好。Mybatis写的动态SQL说到底还是SQL,而不是java代码或者java代码拼字符串。程序员特别排斥几件事:
然而,另外有一派观点,你看人家国外的程序员怎么都用JPA?你不去学习新东西,还不让别人用?JPA使用很方便啊,唯一缺点就是复杂关联SQL支持差一点,但是只要你学一下也还可以支持啊,你们这是劣币驱逐良币。如果经过很好的实体关系模型的设计,JPA显然是最优解,程序员写的SQL还真不如JPA根据实体关系生成的SQL。笔者要说,这种观点也是有道理的。但是,笔者要说并不是国内程序员不愿意学习,而是另有原因。
说完以上几点,Mybatis为什么在国内会有如此多的使用者及使用厂商就不难理解了。Mybatis还可以使用如:Mybatis-plus或者代码自动生成来弥补易用性上的不足。JPA的身材、家室、性格样样都是满分,就是脸长得磕碜点难以处理社交关系。Mybatis虽说在各方面都不优秀,身材还可以、样貌也还说得过去、性格也还好。关键是你说什么都听你的,还有愿意帮他化妆的朋友。要你说你选哪一个?
那么,有的人会说,你这是抬杠?国外就没有受众数量多、功能性强的互联网应用了么?恐怕比国内还多吧,这个也是事实。但是从比例上讲还是国内更多,比例决定开发人员选择技术的方向。这也导致了一个惯性思维,他们平时就用JPA学习训练,所以写大型服务应用的时候也用JPA。那么,他们写JPA会写复杂SQL么?答案是很少会用到,甚至有的国外公司就明令禁止写关联查询SQL。那怎么办?不用关联SQL怎么开发业务需求?不会啊。
国内现在也有越来越多的公司,进行微服务的落地,然而真正落地比较好的企业少之又少。这和多表关联查询有什么关系?我们先来实现这样一个需求:根据用户id去查询该用户所具有的权限。
那么有的人会说,访问多个接口一定比访问一个接口更慢吧!这个真的不一定。如果我们做微服务,一定是我们的应用规模及数据量到达了一定程度。也一定会考虑分表分库、负载均衡、服务拆分细化等问题,当分布式的开发方式被应用越多,多表关联查询使用的机会也就越少。拆分后的服务由于功能单一、负载分流、数据分库存储量级更小等原因,访问速度往往比大数据量数据集中存储、多服务集中部署的应用会更快。
问题回来了,不用关联SQL怎么开发程序?总的来说就是通过合理的服务拆分、数据库拆分、应用的界面数据的组织关系的合理的设计,团队拥有比较好的微服务落地经验,是可以实现不使用关联查询SQL开发应用的。大家也知道,NOSQL越来越流行,绝大部分的NOSQL数据库都没有所谓的关联关系。
总结一下笔者的观点:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/m0_53157173/article/details/121556799
内容来源于网络,如有侵权,请联系作者删除!