Java数据库访问方案探究
【引言】
在我们书写程序的过程中,操作数据库是非常重要的一环,本文我们会探讨一下在Java编程语言中有哪些比较有代表性的技术用以访问数据库的数据。
【Apache Commons DbUtils】
【简介】
Commons DbUtils库提供了一组工具类,旨在使JDBC的操作使用更加简单。我们知道,在JDBC操作中,资源清理部分是最无趣且容易出错的工作。Commons DbUtils将所有的清理任务从代码中抽象出来,让你在使用JDBC的时候,只需要做你真正想做的事情:
查询和更新数据。
使用DbUtils的一些优点是:
l 不存在资源泄露的可能性。正确的JDBC编码并不难,但很费时,也很繁琐。这往往会导致连接泄漏,可能难以追踪。
l 更干净,更清晰的持久化代码。在数据库中用于数据预留操作所需的代码量大大减少。你可以更专注于业务逻辑代码,而不会因为资源清理而变得杂乱无章。
l 用ResultSets数据自动填充JavaBean属性。 你不需要通过调用setter方法将列值手动复制到Bean实例中。ResultSet的每一行数据可以用一个完整填充的Bean实例来表示。
【设计初衷】
DbUtils的设计是为了:
l 短小精悍 -- 能够在短时间内理解整个包的内容。
l 透明 – DbUtils只做两件事情:查询和清理。
l 快速 -- 你不需要创建无数个临时对象来使用DbUtils。
它不是:
l 一个对象/关系型的桥梁。 DbUtils是为希望使用JDBC的开发者而准备的。
l 一个数据访问对象(DAO)框架 - DbUtils可以用来构建一个DAO框架。
l 一个面向对象的一般数据库对象的抽象,如表、列或PrimaryKey。
l 一个任何类型的重量级框架----这里的目标是成为一个简单直接且易于使用的JDBC帮助库。
【使用案例】
DbUtils的核心类/接口是QueryRunner和ResultSetHandler
// 创建一个ResultSetHandler实现,把第一行变成一个Object[]
ResultSetHandler<Object[]> h = new ResultSetHandler<Object[]>() {
public Object[] handle(ResultSet rs) throws SQLException {
if (!rs.next()) {
return null;
}
ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
Object[] result = new Object[cols];
for (int i = 0; i < cols; i++) {
result[i] = rs.getObject(i + 1);
}
return result;
}
};
// 从给定的数据源来创建一个QueryRunner
QueryRunner run = new QueryRunner(dataSource);
// 执行查询并从处理程序中获取结果
Object[] result = run.query("SELECT * FROM Person WHERE name=?", h, "Xiao Ming");
插入或更新数据
QueryRunner run = new QueryRunner( dataSource );
try
{
// 执行SQL更新语句,并返回更新的数量
int inserts = run.update( "INSERT INTO Person (name,height) VALUES (?,?)",
"Xiao Ming", 1.82 );
int updates = run.update( "UPDATE Person SET height=? WHERE name=?",
2.05, "Xiao Ming" );
}
catch(SQLException sqle) {
// 处理异常
}
异步执行调用
ExecutorCompletionService<Integer> executor = new ExecutorCompletionService<Integer>(
Executors.newCachedThreadPool());
AsyncQueryRunner asyncRun = new AsyncQueryRunner(dataSource);
try {
// 为更新调用创建一个Callable
Callable<Integer> callable = asyncRun.update("UPDATE Person SET height=? WHERE name=?", 2.05, "Xiao Ming");
// 将Callable提交给执行者
executor.submit(callable);
} catch (SQLException sqle) {
// 处理异常
}
// 等待某个时机执行,或在另一线程中执行
try {
// 获取更新的结果
Integer updates = executor.take().get();
} catch (InterruptedException ie) {
// 处理异常
}
JavaBean数据返回
单一对象
QueryRunner run = new QueryRunner(dataSource);
// 使用BeanHandler实现来转换第一行数据为一个Bean示例
ResultSetHandler<Person> h = new BeanHandler<Person>(Person.class);
// 执行语句,返回一个JavaBean实例对象
Person p = run.query(
"SELECT * FROM Person WHERE name=?", h, "Xiao Ming");
对象列表
QueryRunner run = new QueryRunner(dataSource);
// 使用BeanListHandler实现来转换所有的行为一个对象列表
ResultSetHandler<List<Person>> h = new BeanListHandler<Person>(Person.class);
// 执行SQL语句并将结果返回到对象列表
List<Person> persons = run.query("SELECT * FROM Person", h);
【License】
Apache License 2.0
【最新版本】
1.7于 2017年7月20日
【官方网站】
http://commons.apache.org/proper/commons-dbutils/
【MyBatis】
【简介】
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码,设置参数以及获取结果集的工作。 MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
【安装】
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
【XML配置使用案例】
调用例子:
Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
//Create a new student object
Student student = new Student("Xiao Ming", "test@gmail.com" );
//Insert student data
session.insert("Student.insert", student);
System.out.println("record inserted successfully");
session.commit();
session.close();
学生类定义:
public class Student {
private int id;
private String name;
private String email;
}
SqlMapConfig.xml:
<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default = "development">
<environment id = "development">
<transactionManager type = "JDBC"/>
<dataSource type = "POOLED">
<property name = "driver" value = ""/>
<property name = "url" value = ""/>
<property name = "username" value = ""/>
<property name = "password" value = ""/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource = "mybatis/Student.xml"/>
</mappers>
</configuration>
Student.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace = "Student">
<insert id = "insert" parameterType = "Student">
INSERT INTO STUDENT (NAME, EMAIL ) VALUES (#{name}, #{email});
<selectKey keyProperty = "id" resultType = "int" order = "AFTER">
select last_insert_id() as id
</selectKey>
</insert>
</mapper>
【注释使用案例】
注释配置
@Configuration
@MapperScan("your-package")
public class PersistenceConfig {
@Bean
public DataSource dataSource() {
...
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
}
基本对象操作
@Insert("Insert into person(name) values (#{name})")
public Integer save(Person person);
@Update("Update Person set name= #{name} where personId=#{personId}")
public void updatePerson(Person person);
@Delete("Delete from Person where personId=#{personId}")
public void deletePersonById(Integer personId);
@Select("SELECT person.personId, person.name FROM person
WHERE person.personId = #{personId}")
Person getPerson(Integer personId);
多对多关联
@Select("select addressId, streetAddress, personId from address
where personId=#{personId}")
public Address getAddresses(Integer personId);
@Results(value={@Result(property="addresses",javaType=List.class,column="personId",many=@Many(select="getAddresses"))})
返回Map
@Select("select * from Person")
@MapKey("personId")
Map<Integer, Person> getAllPerson();
动态SQL
public class MyBatisUtil {
// ...
public String getPersonByName(String name){
return new SQL() {{
SELECT("*");
FROM("person");
WHERE("name like #{name} || '%'");
}}.toString();
}
}
@SelectProvider(type=MyBatisUtil.class, method="getPersonByName")
public Person getPersonByName(String name);
调用存储过程
@Select(value = "{CALL getPersonByProc(#{personId,mode=IN,jdbcType=INTEGER})}")
@Options(statementType = StatementType.CALLABLE)
public Person getPersonByProc(Integer personId);
【License】
Apache License 2.0
【最新版本】
3.5.4于 2020年3月9日
【官方网站】
【MyBatis-Plus】
【简介】
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
MyBatis-Plus可以自动注入基本的SQL片段,有一个强大而灵活的where条件封装器,使用它可以为你节省大量的开发时间。
MyBatis-Plus有许多有用的插件(如代码生成器、自动分页、性能分析等),它基本上提供了您需要的一切。
【安装】
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
【配置】
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(QuickStartApplication.class, args);
}
}
【使用案例】
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
public interface UserMapper extends BaseMapper<User> {
}
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
Assert.assertEquals(5, userList.size());
userList.forEach(System.out::println);
}
【License】
Apache License 2.0
【最新版本】
3.3.1.tmp于 2020年1月27日
【官方网站】
【Hibernate】
Hibernate ORM使开发人员能够更容易地编写使用数据库访问的应用程序。作为一个对象/关系型映射(ORM)框架,Hibernate关注的是数据的持久性,这一点特别适用于关系型数据库。
【安装】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
【使用案例】
创建JPA实体类
@Entity
@Table(name="TBL_EMPLOYEES")
public class EmployeeEntity {
@Id
@GeneratedValue
private Long id;
@Column(name="first_name")
private String firstName;
@Column(name="last_name")
private String lastName;
@Column(name="email", nullable=false, length=200)
private String email;
@Override
public String toString() {
return "EmployeeEntity [id=" + id + ", firstName=" + firstName +
", lastName=" + lastName + ", email=" + email + "]";
}
}
创建JPA存储库
@Repository
public interface EmployeeRepository extends JpaRepository<EmployeeEntity, Long> {
}
使用存储库
@Autowired
EmployeeRepository repository;
Optional<EmployeeEntity> emp = repository.findById(2L);
logger.info("Employee id 2 -> {}", emp.get());
【License】
LGPL2.1
【最新版本】
5.4.13于 2020年3月26日
【官方网站】
https://github.com/hibernate/hibernate-orm
【JdbcTemplate】
Spring JDBC中的所有类都分为四个独立的包。
core - JDBC的核心功能。这个包下的一些重要类包括JdbcTemplate、SimpleJdbcInsert、SimpleJdbcCall和NamedParameterJdbcTemplate。
datasource - 用于访问数据源的实用类。它也有各种数据源实现,用于测试Jakarta EE容器外的JDBC代码。
object - 以面向对象的方式访问DB。它允许执行查询并将结果作为业务对象返回。它还可以将查询结果映射到业务对象的列和属性之间。
support--支持core和object下的类。如:SQLException支持。
【安装】
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
【配置】
@Configuration
@ComponentScan("your package")
public class SpringJdbcConfig {
@Bean
public DataSource mysqlDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("");
dataSource.setUrl("");
dataSource.setUsername("");
dataSource.setPassword("");
return dataSource;
}
}
【使用案例】
基本查询
int result = jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM EMPLOYEE", Integer.class);
插入数据
public int addEmplyee(int id) {
return jdbcTemplate.update(
"INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)", id, "Bill", "Gates", "USA");
}
有命名参数的查询
例子1:
SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("id", 1);
return namedParameterJdbcTemplate.queryForObject(
"SELECT FIRST_NAME FROM EMPLOYEE WHERE ID = :id", namedParameters, String.class);
例子2:
Employee employee = new Employee();
employee.setFirstName("James");
String SELECT_BY_ID = "SELECT COUNT(*) FROM EMPLOYEE WHERE FIRST_NAME = :firstName";
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(employee);
return namedParameterJdbcTemplate.queryForObject(
SELECT_BY_ID, namedParameters, Integer.class);
将查询结果映射到Java对象
public class EmployeeRowMapper implements RowMapper<Employee> {
@Override
public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
Employee employee = new Employee();
employee.setId(rs.getInt("ID"));
employee.setFirstName(rs.getString("FIRST_NAME"));
employee.setLastName(rs.getString("LAST_NAME"));
employee.setAddress(rs.getString("ADDRESS"));
return employee;
}
}
String query = "SELECT * FROM EMPLOYEE WHERE ID = ?";
List<Employee> employees = jdbcTemplate.queryForObject(
query, new Object[] { id }, new EmployeeRowMapper());
异常处理
public class CustomSQLErrorCodeTranslator extends SQLErrorCodeSQLExceptionTranslator {
@Override
protected DataAccessException
customTranslate(String task, String sql, SQLException sqlException) {
if (sqlException.getErrorCode() == 23505) {
return new DuplicateKeyException(
"Custom Exception translator - Integrity constraint violation.", sqlException);
}
return null;
}
}
CustomSQLErrorCodeTranslator customSQLErrorCodeTranslator =
new CustomSQLErrorCodeTranslator();
jdbcTemplate.setExceptionTranslator(customSQLErrorCodeTranslator);
使用SimpleJdbc类的JDBC操作
添加数据
例子1:
SimpleJdbcInsert simpleJdbcInsert =
new SimpleJdbcInsert(dataSource).withTableName("EMPLOYEE");
public int addEmplyee(Employee emp) {
Map<String, Object> parameters = new HashMap<String, Object>();
parameters.put("ID", emp.getId());
parameters.put("FIRST_NAME", emp.getFirstName());
parameters.put("LAST_NAME", emp.getLastName());
parameters.put("ADDRESS", emp.getAddress());
return simpleJdbcInsert.execute(parameters);
}
例子2:
SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("EMPLOYEE")
.usingGeneratedKeyColumns("ID");
Number id = simpleJdbcInsert.executeAndReturnKey(parameters);
System.out.println("Generated id - " + id.longValue());
使用SimpleJdbcCall访问存储过程
SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(dataSource)
.withProcedureName("READ_EMPLOYEE");
public Employee getEmployeeUsingSimpleJdbcCall(int id) {
SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id);
Map<String, Object> out = simpleJdbcCall.execute(in);
Employee emp = new Employee();
emp.setFirstName((String) out.get("FIRST_NAME"));
emp.setLastName((String) out.get("LAST_NAME"));
return emp;
}
批量作业
使用JdbcTemplate的基本批量操作
public int[] batchUpdateUsingJdbcTemplate(List<Employee> employees) {
return jdbcTemplate.batchUpdate("INSERT INTO EMPLOYEE VALUES (?, ?, ?, ?)",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setInt(1, employees.get(i).getId());
ps.setString(2, employees.get(i).getFirstName());
ps.setString(3, employees.get(i).getLastName());
ps.setString(4, employees.get(i).getAddress();
}
@Override
public int getBatchSize() {
return 50;
}
});
}
使用NamedParameterJdbcTemplate的批量操作
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(employees.toArray());
int[] updateCounts = namedParameterJdbcTemplate.batchUpdate(
"INSERT INTO EMPLOYEE VALUES (:id, :firstName, :lastName, :address)", batch);
return updateCounts;
【License】
Apache License 2.0
【最新版本】
5.2.5.RELEASE于 2020年3月24日
【官方网站】
https://github.com/spring-projects/spring-framework
【Spring Data JDBC】
Spring Data JDBC是Spring Data家族的一部分,它使得JDBC存储库的实现变得容易。该模块提供了基于JDBC的数据访问层的增强支持。它使得构建使用数据访问技术的Spring应用程序更加容易。
Spring Data JDBC的目标是简单。为了实现这个目标,它不提供缓存、惰性加载、后写或许多其他JPA的特性。这使得Spring Data JDBC成为一个简单的、有限的、有些争议的ORM。
总量根
Spring数据存储库的灵感来源于Eric Evans在《Domain Driven Design》一书中描述的存储库。这样做的一个结果是,每个总量根都有一个存储库。总量根是同一本书中的另一个概念,它是一个实体,这个实体控制着其他实体的生命周期,而这些实体共同构成了一个总量。总量是你的模型的一个子集,它在方法调用到你的总量根这个过程中是保持一致的。
Spring Data JDBC鼓励按照上面这些想法来建模。
特点
l 用于简单聚合的CRUD操作与可定制的命名策略。
l 支持@Query注释。
l 支持MyBatis查询。
l 事件。
l 通过引入@EnableJdbcRepositories,实现了基于JavaConfig的版本库配置。
【安装】
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jdbc</artifactId>
<version>${version}.RELEASE</version>
</dependency>
【配置】
@Configuration
@EnableJdbcRepositories
class ApplicationConfig extends AbstractJdbcConfiguration {
@Bean
public DataSource dataSource() {
return …;
}
@Bean
public NamedParameterJdbcTemplate namedParameterJdbcTemplate(DataSource dataSource) {
return new NamedParameterJdbcTemplate(dataSource);
}
}
【使用案例】
public interface PersonRepository extends CrudRepository<Person, Long> {
@Query("SELECT * FROM person WHERE lastname = :lastname")
List<Person> findByLastname(String lastname);
@Query("SELECT * FROM person WHERE firstname LIKE :firstname")
List<Person> findByFirstnameLike(String firstname);
}
@Service
public class MyService {
private final PersonRepository repository;
public MyService(PersonRepository repository) {
this.repository = repository;
}
public void doWork() {
repository.deleteAll();
Person person = new Person();
person.setFirstname("Jens");
person.setLastname("Schauder");
repository.save(person);
List<Person> lastNameResults = repository.findByLastname("Schauder");
List<Person> firstNameResults = repository.findByFirstnameLike("Je%");
}
}
【License】
Apache License 2.0
【最新版本】
1.1.6.RELEASE于 2020年3月30日
【官方网站】
https://spring.io/projects/spring-data-jdbc
【Spring Data JPA】
Spring Data JPA是Spring Data家族的一部分,它可以轻松实现基于JPA的存储库。这个模块处理了对基于JPA的数据访问层的增强支持。它使得构建使用数据访问技术的Spring驱动的应用程序更加容易。
在相当长的一段时间里,实现一个应用程序的数据访问层一直是很麻烦的。为了执行简单的查询、分页和审计,需要编写太多的模板代码。Spring Data JPA的目的是改善数据访问层的实现。作为开发者,你可以编写你的存储库接口,包括自定义的查找器方法,Spring将自动提供实现。
特点
l 支持基于Spring和JPA构建资源库的复杂支持。
l 支持Querydsl谓词,从而支持类型安全的JPA查询。
l 域类的透明审计
l 支持分页显示,动态查询执行,能够集成自定义的数据访问代码
l 在引导时验证@Query注释的查询的验证
l 支持基于XML的实体映射
l 通过引入@EnableJpaRepositories来配置基于JavaConfig的版本库。
在上面的内容里,我们探讨了如下几种访问数据库的技术: DbUtils, MyBatis, Mybatis Plus, Hibernate, JdbcTemplate,Spring Data JDBC, Spring Data JPA。 其中DbUtils相对老旧一些,因此如果在选取技术时在意社区活跃度的话,可以规避一下这个程序库。
- 点赞
- 收藏
- 关注作者
评论(0)