围绕Spring Boot 生态_从JDBC到ORM框架_细说各类框架优缺点及技术选型

开篇:从JDBC到ORM框架的编码方式的演变

本文博客的整体流程:基于开发者编码角度理解技术选型

ORM框架产生的背景——》目前主流的ORM框架——》如何使用ORM框架——》企业项目中如何做ORM选型——》ORM框架内部原理与源码实现分析

ORM框架产生的背景

1)  起初的编码方法:基于JDBC编码

JDBC(Java Data Base Connectivity,Java 数据库连接)是一种用于执行 SQL 语句来操作数据库的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

说白了 JDBC 就是一套 Java 访问数据库的 API 规范,利用这套规范蔽了各种数据库 API 调用的差异性。当 Java 程序需要访问数据库时,直接调用 JDBC API 相关代码进行操作,JDBC 调用各类数据库的驱动包进行交互,最后数据库驱动包和其对应的数据库通讯,完成 Java 程序操作数据库。

直接在 Java 程序中使用 JDBC 比较复杂,需要 7 步才能完成数据库的操作:

  • 加载数据库驱动
  • 建立数据库连接
  • 创建数据库操作对象
  • 定义操作的 SQL 语句
  • 执行数据库操作
  • 获取并操作结果集
  • 关闭对象,回收资源

通过上面的示例可以看出直接使用 JDBC 来操作数据库比较复杂,因此后期在 JDBC 的基础上又发展出了很多著名的 ORM 框架,其中最为流行的是 Hibernate、MyBatis 和 Spring JDBC

2)目前企业的开发编码方式:基于ORM框架编码

这里先了解一下 Spring JDBC 在 Spring Boot 中的使用。

Spring Boot 针对 JDBC 的使用提供了对应的 Starter 包:spring-boot-starter-jdbc,它其实就是在 Spring JDBC 上做了进一步的封装,方便在 Spring Boot 生态中更好的使用 JDBC

采用SpringBoot项目结构学习其整合的相关技术-JDBC->ORM框架:========================================》

案例演示流程:1.单数据源——》2.多数据源

案例演示工程目录结构:

下面做一些通用的开发前期数据与配置环境的准备工作: 

数据连接配置信息:

导入相关依赖包:POM.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.neo</groupId>
	<artifactId>spring-boot-jdbc</artifactId>
	<version>1.0.0</version>
	<packaging>jar</packaging>

	<name>spring-boot-jdbc</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.0.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId><version>5.1.6</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>


</project>

 创建数据库ssm与users表,做测试使用

 如何使用ORM框架:

SpringJDBC核心对象:

SpringJDBC操作数据库的核心是使用如下类,

org.springframework.jdbc.core.JdbcTemplate;

   从下图类的继承体系看出,JdbcTemplate主要的功能就是对 数据库访问类与数据库操作接口 的处理

 继续展开类的体系图:JdbcAccessor类主要做了数据源的配置与事物处理

JdbcOperations接口定义了一些常用的数据库操作方法 

InitializingBean 负责读取配置文件做初始化操作

下面我们基于SpringJDBC提供的API做数据库操作:

 采用MVC三层架构组织项目:只不过在这里我用不到C层

M层:

package com.neo.model;

public class User  {

    private Long id;
    private String name;
    private String password;
    private int age;

    public User() {
    }

    public User(String name, String password, int age) {
        this.name = name;
        this.password = password;
        this.age = age;
    }

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                '}';
    }
}

DAO:

package com.neo.repository;

import com.neo.model.User;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public interface UserRepository  {

    int save(User user);

    int update(User user);

    int delete(long id);

    List<User> findALL();

    User findById(long id);
}
package com.neo.repository.impl;

import com.neo.model.User;
import com.neo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

@Repository
public class UserRepositoryImpl implements UserRepository {
    @Autowired
    @Qualifier("primaryJdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public int save(User user) {
        return jdbcTemplate.update("INSERT INTO users(name, password, age) values(?, ?, ?)",
              user.getName(), user.getPassword(), user.getAge());
    }

    @Override
    public int update(User user) {
        return jdbcTemplate.update("UPDATE users SET name = ? , password = ? , age = ? WHERE id=?",
               user.getName(), user.getPassword(), user.getAge(), user.getId());
    }

    @Override
    public int delete(long id) {
        return jdbcTemplate.update("DELETE FROM users where id = ? ",id);

    }

    @Override
    public User findById(long id) {
        return jdbcTemplate.queryForObject("SELECT * FROM users WHERE id=?", new Object[] { id }, new BeanPropertyRowMapper<User>(User.class));
    }

    @Override
    public List<User> findALL() {
        return jdbcTemplate.query("SELECT * FROM users", new UserRowMapper());
        // return jdbcTemplate.query("SELECT * FROM users", new BeanPropertyRowMapper(User.class));
    }

    class UserRowMapper implements RowMapper<User> {
        @Override
        public User mapRow(ResultSet rs, int rowNum) throws SQLException {
            User user = new User();
            user.setId(rs.getLong("id"));
            user.setName(rs.getString("name"));
            user.setPassword(rs.getString("password"));
            user.setAge(rs.getInt("age"));
            return user;
        }
    }

}

下面做下单元测试工作:

package com.neo.repository;

import com.neo.model.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {

	@Autowired
    private UserRepository userRepository;



	@Test
	public void testSave() {
		User user =new User("neo","123456",30);
		userRepository.save(user);
	}

	@Test
	public void testUpdate() {
		User user =new User("neo","123456",18);
		user.setId(1L);
		userRepository.update(user);
	}

	@Test
	public void testDetele() {
		userRepository.delete(1L);
	}

	@Test
	public void testQueryOne()  {
		User user=userRepository.findById(1L);
		System.out.println("user == "+user.toString());
	}

	@Test
	public void testQueryAll()  {
		List<User> users=userRepository.findALL();
		for (User user:users){
			System.out.println("user == "+user.toString());
		}
	}

}

最终测试效果如下:

看下数据库中的变化:ok 执行没问题

 

SpringJDBC多数据源如何实现? 

主要做两个任务:1.配置多数据源配置信息 2.封装不同数据源对象

package com.neo.config;

import org.springframework.beans.factory.annotation.Qualifier;
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;

import javax.sql.DataSource;

/**
 * @author Cheri
 * @title: DataSourceConfig 多数据源配置类
 * @projectName spring-boot-jdbc
 * @description: TODO
 * @date 2019/6/4 15:33
 */
@Configuration
public class DataSourceConfig {
    @Primary
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @Qualifier("secondaryDataSource")
    @ConfigurationProperties(prefix="spring.datasource.secondary")
    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);
    }
}

 如何使用多数据源呢?

如果方法中传输了 JdbcTemplate,方法内就会使用传递的 JdbcTemplate 进行操作,如果传递的 JdbcTemplate 为空,使用默认的 JdbcTemplate 连接操作。

package com.neo.repository;

import com.neo.model.User;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * @author Cheri
 * @title: MutiUserRepository
 * @projectName spring-boot-jdbc
 * @description: TODO
 * @date 2019/6/4 15:44
 */
public interface MutiUserRepository {
    int save(User user, JdbcTemplate jdbcTemplate);
}
package com.neo.repository.impl;

import com.neo.model.User;
import com.neo.repository.MutiUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * @author Cheri
 * @title: MutiUserRepositoryImpl
 * @projectName spring-boot-jdbc
 * @description: TODO
 * @date 2019/6/4 16:29
 */
@Repository
public class MutiUserRepositoryImpl implements MutiUserRepository {
    @Autowired
    private JdbcTemplate primaryJdbcTemplate;

    @Override
    public int save(User user,JdbcTemplate jdbcTemplate) {
        if(jdbcTemplate == null){
            jdbcTemplate= primaryJdbcTemplate;
        }
        return jdbcTemplate.update("INSERT INTO users(name, password, age) values(?, ?, ?)",
                user.getName(), user.getPassword(), user.getAge());
    }
}

执行单元测试一下看看:是否可以同时向两个数据源插入数据

package com.neo.repository;

import com.neo.model.User;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTests {

	@Autowired
    private UserRepository userRepository;
	@Autowired
	private MutiUserRepository mutiUserRepository;

	@Autowired
	private JdbcTemplate primaryJdbcTemplate;
	@Autowired
	private JdbcTemplate secondaryJdbcTemplate;

	@Test
	public void testSave2() {
		User user =new User("smile","123456",30);
		mutiUserRepository.save(user,primaryJdbcTemplate);
		mutiUserRepository.save(user,secondaryJdbcTemplate);
	}
}

 最终执行结果如下图:

都存在一条 name 为 smile 的用户信息,说明多数据源插入数据成功,其他方法的测试大体相同。这样在项目中,我们想使用哪个数据源操作数据库时,只需要传入数据源对应的 JdbcTemplate 实例即可。 


下面我们重点研究下SpringJDBC的工作原理与源码分析:

了解下我们进行save后SpringJDBC都做了啥?

设置断点,我们跟踪下SpringJDBC内部三大核心对象如何操作的:以save为例


 最核心的代码:


protected int update(PreparedStatementCreator psc, final PreparedStatementSetter pss)
			throws DataAccessException {
		
		//构建 PreparedStatementCallback
		return execute(psc, new PreparedStatementCallback<Integer>() {
			public Integer doInPreparedStatement(PreparedStatement preparedStatement) throws SQLException {
				try {
					if (pss != null) {
						//给preparedStatement设置参数
						pss.setValues(preparedStatement);
					}
					int rows = preparedStatement.executeUpdate();
					return rows;
				}	finally {
					if (pss instanceof ParameterDisposer) {
						((ParameterDisposer) pss).cleanupParameters();
					}
				}		
			}
		});
	}

 整体流程:传入sql与执行参数 ——》对执行参数进一步封装,并构造执行器Creator——》执行update操作,在update中进行参数赋值与设置返回值设置 最终内部调用的是执行器的executeUpdate方法

里涉及到 JDK1.8的函数式接口(Functional Interface)写法 ——》下图中(ps)——{}

发布了234 篇原创文章 · 获赞 12 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Coder_Boy_/article/details/90769349