学习杂谈:持久层框架那些链接数据库的操作

为了进行数据的持久化,和数据库操作是必须的操作。 本文探讨不同的持久层框架链接数据库的操作。

JDBC

JDBC 为多个不同的数据库实现提供了同一的接口,使得我们可以通过JDBC来操作不同的数据库,而不需要担心数据库的具体实现。

要在Java项目中使用JDBC,首先需要引进jar包。假设我们需要链接的数据库是mysql,则需要引进mysql相关的jar包。

使用Maven是进行包管理是相当便捷的做法,要使用maven,首先我们需要新建一个Maven的项目或者模块。这里就用Java唯一指定IDE IntlliJ IDEA 来演示。

1
2
3
4
5
6
<!--        mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

要链接数据库,自然少不了要链接数据库的参数,比如Driver,URL,以及Username,Password等等。所以我们需要新建一个properties 来存放这些数据,而不是写死在Java代码中。

1
2
3
4
DRIVER=com.mysql.cj.jdbc.Driver
URL=mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8
USERNAME=root
PASSWORD=root
  1. 加载驱动

数据库有着不同的实现,mysql,oracle,monogDB,SQL Server等等,而每个数据库都提供一个驱动程序,驱动程序是与数据库系统进行通信的软件组件。每种数据库系统都有自己的驱动程序,这是因为不同数据库系统采用不同的协议和通信方式。

驱动程序充当Java程序与数据库之间的桥梁,负责处理与数据库的连接、执行SQL语句以及获取和处理查询结果等任务。它们提供了一组标准的接口和方法,使得Java程序能够通过统一的方式与不同数据库进行交互,而不需要了解底层数据库的具体实现细节。

加载数据库驱动程序的常用方式是使用Class.forName()方法,例如:

在这里,我们需要加载mysql的驱动程序,MySQL数据库的驱动程序类名为com.mysql.jdbc.Driver(旧版)或com.mysql.cj.jdbc.Driver(新版)。

1
Class.forName("com.mysql.cj.jdbc.Driver"); // 加载MySQL驱动程序

一旦数据库驱动程序加载成功,就可以通过JDBC建立与数据库的连接并执行相关操作。

  1. 建立数据库连接

要和数据库进程操作,我们需要和数据库建立一个会话关系,就像在web中一样,在web中,这个会话被称为httpSession, 而在数据库中,也同样存在着这个会话。

一个会话代表了客户端与数据库之间的通信通道,它允许客户端应用程序与数据库服务器进行交互。

1
2
// 2.连接数据库
Connection connection = DriverManager.getConnection(url, username, password);

在JDBC中,通过DriverManager.getConnection()方法建立数据库连接时,实际上是在建立一个会话。该方法返回一个Connection对象,表示与数据库的连接。这个Connection对象封装了与数据库服务器的底层通信细节,包括连接信息、会话状态和数据传输等。

通过会话,可以在连接期间执行多个SQL语句,保持数据库连接的状态。这样可以避免每次执行SQL语句都重新建立连接,提高性能和效率。当不再需要连接时,可以调用Connection对象的close()方法关闭连接,释放相关资源。

  1. 创建执行SQL语句的对象

有了链接,我们就可以通过Connection对象的createStatement()方法创建Statement对象或prepareStatement()方法创建PreparedStatement对象。

Statement用于执行静态的SQL语句,而PreparedStatement用于执行预编译的SQL语句,可以提高性能和安全性,防止SQL注入。

  1. 执行SQL语句:

通过StatementPreparedStatement对象的executeQuery()方法执行查询语句,返回一个ResultSet对象,其中包含查询结果;通过executeUpdate()方法执行更新语句(如插入、更新、删除),返回受影响的行数。

  1. 处理结果集:

但是仅仅是往数据库中传SQL语句,显然是不够的,我们还需要接受数据库返回的数据。

数据库的操作有四种,我们将其称为CRUD,即CREATE, READ, UPDATE and DELETE,也就是我们常说的增删改查。其中查询操作返回一个结果集Result,其他的操作都返回一个int类型的整数,代表受影响的行数。

所以,我们只需要编写两个函数,处理这两种不同的操作。

对于查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//执行查询, 返回结果集
public static ResultSet executeQuery(Connection conn,String query,Object[] params) throws SQLException, ClassNotFoundException{
ResultSet resultSet = null;
if(conn != null && !query.isEmpty() && params != null){
//预处理
PreparedStatement statement = conn.prepareStatement(query);
for (int i = 0; i < params.length;i++){
//设置SQL 语句中的参数
statement.setObject(i+1,params[i]);
}
//执行
resultSet = statement.executeQuery();
}
return resultSet;
}

对于更新删除,增加操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//执行更新, 返回影响行数
public static int executeUpdate(Connection conn,String query,Object[] params) throws SQLException {
int affectedRows = 0;
if(conn != null && !query.isEmpty() && params != null){
//预处理
PreparedStatement statement = conn.prepareStatement(query);
for (int i = 0; i < params.length;i++){
//设置SQL 语句中的参数
statement.setObject(i+1,params[i]);
}
//执行
affectedRows = statement.executeUpdate();
}
return affectedRows;
}
  1. 关闭连接和资源

使用完ResultSetStatementConnection等对象后,需要调用它们的close()方法来关闭连接和释放资源,以免造成内存泄漏和资源浪费。

我们可以编写一个函数来专门关闭这些资源,但这不是必须的,你也可以手动关闭。

1
2
3
4
5
6
//释放链接资源
public static void close(Connection conn, PreparedStatement pstmt, ResultSet resSet) throws SQLException {
if(resSet != null) resSet.close();
if(pstmt != null) pstmt.close();
if(conn != null) conn.close();
}

果然好麻烦,但是还没有结束,查询操作返回了一个结果集,我们还需要对这个结果集进行ORM处理,即Object Relationship Mapping。把数据库中的entity映射到一个bean上,这是最麻烦的。因为我们需要一一个处理这些字段和属性的对应关系。

下面这段代码就是一个一个例子,可以看到,对于每一个对象的属性值,我们需要一一的找到对应的字段值。这还只是查询一张表,在多表联合查询的过程中,我们需要封装的对象数量更多。我们不应该花太多时间处理这些重复而又无意义的事。

下面介绍的持久层框架,就是对JDBC的二次封装,免去了我们编写ORM的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public User findById(int id) {
Object[] params = {id};
ResultSet set;
sql = "SELECT * FROM smbms_user,smbms_role Where smbms_user.userRole = smbms_role.id and smbms_user.id = ?";
try {
conn = DB.getConnection();
set = DB.executeQuery(conn, sql,params);
if(set != null){
user = new User();
while (set.next()) {
user.setUserRoleName(set.getString("roleName"));
user.setUserPassword(set.getString("userPassword"));
user.setId(set.getInt("id"));
user.setUserCode(set.getString("userCode"));
user.setUserName(set.getString("userName"));
user.setAddress(set.getString("address"));
user.setBirthday((Date) set.getObject("birthday"));
user.setGender(set.getInt("gender"));
user.setPhone(set.getString("phone"));
user.setIdPicPath(set.getString("idPicPath"));
user.setUserRole(set.getInt("userRole"));
user.setCreatedBy(set.getInt("createdBy"));
user.setCreationDate( set.getDate("creationDate"));
user.setModifyBy(set.getInt("modifyBy"));
user.setModifyDate(set.getDate("modifyDate"));
user.setWorkPicPath("workPicPath");
}
} else {
return null;
}
//关闭链接和结果集
DB.close(conn,null,set);
} catch (SQLException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return user;
}

MyBatis

MyBatis是一个优秀的持久层框架,是对JDBC的二次封装。该框架大大简化了ORM过程,MyBatis可以通过字段和属性名自动进行ORM操作。

  1. 要使用MyBatis,当然需要引入相关的jar包:
1
2
3
4
5
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
  1. 编写MyBatis配置文件,这个配置文件我们可以从官网中拿到对应的模版,这是mybatis的核心配置文件,通过编写配置文件,我们可以得到mybatis的不同表现。

最好将这个配置文件编写在根路径下,放在资源文件有是再好不过的选择

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 从db.properties读取数据库属性-->
<properties resource="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${DRIVER}"/>
<property name="url" value="${URL}"/>
<property name="username" value="${USERNAME}"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
</configuration>

在这个配置文件中,我们只配置的数据源,后续的配置随着讲解的深入再一一添加

  1. 编写bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package cn.loulan.bean;

    import lombok.Data;

    @Data
    public class Student {
    private int id;
    private String name;
    private String sex;
    private String major;
    private int age;
    }
  2. 编写接口

和JDBC一样,我们需要编写接口,在接口中定义方法。面向接口编程可以大大降低层与层之间的耦合度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.loulan.dao;

import cn.loulan.bean.Student;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface StudentMapper {
int insert(Student student);
int update(Student student);
Student getStudent(int id);
List<Student> getAll();
int delete(@Param("id") int id);
}

不同的是,由于不需要编写接口实现类了,Mybatis会通过反射的方式在程序运行时生成实现类。所以我们需要使用@Param注解来指定参数,如果参数只有一个则不需要显式的给出,参数类型是复杂类型如java bean时也不需要显式的给出。

  1. 编写mapper

没有了实现了类,但是我们还需要编写mapper文件,其实也可以直接使用注解的方式定义SQL语句,但是这样对于较为复杂的SQL语句代码的可读性将大大降低。所以我们还是基于xml的的方式。

mapper使用xml格式,且需要保证与接口名称相同。 在mapper中,要指定命名空间,这个命名空间指向接口类,使用接口类的全限定名。

1
2
3
4
5
6
7
8
9
<?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="cn.loulan.dao.StudentMapper">
<select id="getStudent" resultType="cn.loulan.bean.Student">
select * from student where id=#{id};
</select>
</mapper>

和JDBC一样,我们需要和数据库建立连接,拿到会话,在Mybatis中,这个会话被称为Sql Session要拿到这个SqlSession, 还有点麻烦,这里Mybatis使用了一个设计模式,叫做建造者模式。这是GOF23种设计模式的创造型设计模式之一。要理解建造者模式,我们需要首先了解工厂模式。

工厂模式在spring中有着大量的运用,比如动态代理中Bean的实例化就是工厂模式的一个运用。每一个bean都对应一个工厂,用来生成这个bean。这里的工厂模式确实的说是静态工厂模式,也叫做简单工厂模式,这个设计模式并不在GOF23种设计模式之内,但是运用的也比较多,而且简单工厂模式其实算得上是工厂模式的一个特例。

要理解工厂模式,首先我们需要知道为什么需要工厂模式?

面向对象有七大设计原则,第一个便是单一职责原则。顾名思义就是一个类只负责一个职责。我们使用一个类,不需要担心这个是如何被创建出来的,就像在生活中我们买一台电脑不需要关心这个电脑是如何生产出来的。电脑的生产由生产这个电脑的工厂负责。相较于自己造一台电脑,显然去买一台工厂生产的电脑更为现实。

简单工厂模式就是这样思想的一种体验,在软件开发过程中,我们往往需要实例化一些来着同一个父类的对象, 这个父类定义了这个工厂下所以对象的通用方法。为此专门提供一个工厂来帮我们实例化这个对象,我们只需要提供不同的参数,就可以得到不同的实例化对象,在Java开发中,我们常常把工厂类的实例化这个对象的方法定义为一个静态方法,这样,我们就不需要实例化这个类就可以调用这个方法了,这也就是静态工厂模式名字的来源。

简单工厂模式包含三个重要的角色:

  1. 工厂 Factory: 工厂模式的核心角色,这个角色提供了一个静态方法,这个方法负责实例化对象并返回,返回类型为抽象产品。
  2. 抽象产品 Product: 抽象产品是工厂建造的所以具体对象的父类,负责描述所以实例化对象的公共接口。它的引入使得系统更加灵活,在工厂类中只需要定义一个方法,因为所以的具体产品对象都是其子类对象。
  3. 具体产品 ConcreteProduct: 简单工厂模式的创建目标,是抽象产品的具体表现,需要实现抽象产品中定义的方法。

在Mybatis中,便使用工厂模式这一思想。要拿到sqlsession,我们需要先拿到工厂对象,通过读取配置文件,MyBatis会创建一个SqlSessionFactory对象。通过工厂对象的openSession我们就可以拿到这个Sql Session了。SqlSession是与数据库进行交互的主要接口,它封装了数据库会话和执行SQL操作的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package cn.loulan.dao;

import cn.loulan.bean.Student;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.InputStream;

public class TestStudent {
@Test
public void testGetStudent() throws Exception {
//1. 加载mybatis配置文件
InputStream resourceAsStream = TestStudent.class.getClassLoader().getResourceAsStream("mybatis.xml");
//2. 拿到工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
//3. sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//4. 拿到接口实现类
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//5. 关闭sql session释放资源
sqlSession.close();
//6. 输出查询结果
Student student = mapper.getStudent(1);
System.out.println(student);
}
}

到这里Mybatis链接数据库的操作就结束了,虽然还有很多没有设计,比如事务管理,对象关系映射啊,但是这又不是Mybatis教程,要看mybatis教程的话移步博客文章mybatis。

总得来说,mybatis确实相较于直接用JDBC方便很多,自己再也不用处理令人头疼的结果集了,只需要写些配置文件就好了,效率提高了很多。但是听说到了spring boot就更方便了,总感觉学不完啊。

Spring集成Mybatis框架

接下来就是spring集成mybatis框架了,集成spring可以通过依赖注入(Dependency Injection)来降低层与层之间的耦合度,具体来说就是对象不需要我们自己new了,对象间关系的维护也不需要我们自己维护了,全部交给Spring来管理。

spring集成mybatis框架感觉就是把mybatis配置移到了Spring的配置中去,以前是mybatis通过反射实例化对象,变成了spring和mybatis共同完成了。

  1. MyBatis部分:MyBatis通过Mapper接口的代理方式来执行SQL操作。在MyBatis中,通过配置映射文件(Mapper XML)来定义SQL语句和映射关系。MyBatis会根据映射文件中的配置,动态生成Mapper接口的代理对象,实现了Mapper接口中定义的方法。
  2. Spring部分:Spring负责管理和配置应用程序中的各个组件,包括Mapper接口的实例化和依赖注入。在集成MyBatis时,Spring会扫描指定的包路径,检测并实例化Mapper接口的代理对象。Spring提供了@MapperScan注解来自动扫描和实例化Mapper接口。

当Spring实例化Mapper接口时,它会使用MyBatis提供的MapperProxyFactory类来创建Mapper接口的代理对象。该代理对象在执行方法时,会委托给MyBatis的底层实现来执行相应的SQL操作。

通过Spring和MyBatis的协作,实现了Mapper接口的实例化和使用,使得开发者可以在Spring中轻松地使用MyBatis进行数据库操作。同时,Spring还负责管理事务、数据源配置等与数据库相关的事务处理和连接管理工作,与MyBatis相结合提供了更完整的数据库访问解决方案。

以上内容全由chatGPT生成,我是不会的。

继续。

照例,先引入jar包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>6.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.8</version>
</dependency>
<!-- MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.8</version>
</dependency>

差不多就这些了,主要包括spring框架的依赖,mybatis依赖,spring集成mybatis的依赖,德鲁伊的依赖。

累了,不想写了。下次补充。。

Writing Your Own Unix Shell 手写简易Spring框架
You need to set install_url to use ShareThis. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×