《Spring 3.0就这么简单》——1.4 持久层

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 在编写DAO接口的实现类时,大家也许有一个问题:在以上两个DAO实现类中都没有打开/释放Connection的代码,DAO类究竟如何访问数据库呢?前面说过,样板式的操作都被JdbcTemplate封装起来了,JdbcTemplate本身需要一个DataSource,这样它就可以根据需要从DataSource中获取或返回连接。

本节书摘来自异步社区《Spring 3.0就这么简单》一书中的第1章,第1.4节,作者: 陈雄华 , 林开雄著,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.4 持久层

持久层负责数据的访问和操作,DAO类被上层的业务类调用。Spring本身支持多种流行的ORM框架。这里使用Spring JDBC作为持久层的实现技术,关于Spring JDBC的详细内容,请参见第4章的内容。为方便阅读,会对本章涉及的相关知识点进行必要的介绍,所以相信读者在不了解Spring JDBC的情况下,也可以轻松阅读以下的内容。
1.4.1 建立领域对象
领域对象(Domain Object)也称为实体类,它代表了业务的状态,一般来说,领域对象属于业务层,但它贯穿展现层、业务层和持久层,并最终被持久化到数据库中。领域对象使数据库表操作以面向对象的方式进行,为程序的扩展带来了更大的灵活性。领域对象不一定等同于数据库表,不过对于简单的应用来说,领域对象往往都拥有对应的数据库表。

持久层的主要工作就是从数据库表中加载数据并实例化领域对象,或将领域对象持久化到数据表中。由于持久层需要用到领域对象,所以将本属于业务层的领域对象提前到持久层来说明。

景区网站登录模块需要涉及两个领域对象:User和LoginLog,前者代表用户信息,后者代表日志信息,分别对应t_user和t_login_log数据表,领域对象类的包为com.smart.domain。

用户领域对象
用户信息领域对象很简单,可以看成是对t_user表的对象翻译,每个字段对应一个对象属性。User类主要有两类信息,分别为用户名/密码(userName/password)以及最后一次登录的信息(lastIp、lastVisit),其代码如代码清单1-3所示。

代码清单1-3 User.java领域对象

package com.smart.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable{ ③
  private int userId;
  private String userName;
  private String password;
  private String lastIp;
  private Date lastVisit;

//省略get/setXxx方法_
   …
}

登录日志领域对象
用户每次登录成功后,记录一条登录日志,该登录日志包括3个信息,分别是用户ID、登录IP地址和登录时间。一般情况下,还包括退出时间。为了简化实例,仅记录登录时间,登录日志的领域对象如代码清单1-4所示。

代码清单1-4 LoginLog.java

package com.smart.domain;
import java.io.Serializable;
import java.util.Date;
public class LoginLog implements Serializable {
  private int loginLogId;
  private int userId;
  private String ip;
  private Date loginDate;
 //省略get/setXxx方法_
…
}

1.4.2 UserDao
首先定义访问User的DAO,它包括以下3个方法。

getMatchCount():根据用户名和密码获取匹配的用户数。等于1表示用户名/密码正确,等于0表示用户名或密码错误(这是最简单的用户身份认证方法,在实际应用中需要采用诸如密码加密等安全策略)。
findUserByUserName():根据用户名获取User对象。
updateLoginInfo():更新用户积分、最后登录IP地址以及最后登录时间。
下面通过Spring JDBC技术实现这个DAO类,如代码清单1-5所示。

代码清单1-5 UserDao

package com.smart.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Repository;
import com.smart.domain.User;
@Repository ①
public class UserDao {

@Autowired ②
  private JdbcTemplate jdbcTemplate;

  public int getMatchCount(String userName, String password) {
    String sqlStr = " SELECT count(*) FROM t_user "
       + " WHERE user_name =? and password=? ";
    return jdbcTemplate.queryForInt(sqlStr, new Object[] { userName, password });
  }
    …
}

在Spring 1.5以后,可以调用注解的方式定义Bean,较之于XML配置方式,注解配置方式的简单性非常明显,已经被广泛接受,成为一种趋势。所以除非没有办法,否则都应尽量采用注解的配置方式。

这里用@Repository定义了一个DAO Bean,使用@Autowired将Spring容器中的Bean注入进来。关于Spring的注解配置,将会在第2章详细讨论。

传统的JDBC API太底层,即使用户执行一条最简单的数据查询操作,都必须执行如下的过程:获取连接→创建Statement→执行数据操作→获取结果→关闭Statement→关闭结果集→关闭连接,除此之外还需要进行异常处理的操作。如果使用传统JDBC API进行数据访问操作,可能会有1/3以上单调乏味的重复性代码像苍蝇一样驱之不散。

Spring JDBC对传统的JDBC API进行了薄层的封装,将样板式的代码和那些必不可少的代码进行了分离,用户仅需要编写那些必不可少代码,剩余的单调乏味的重复性工作则交由Spring JDBC框架处理。简单来说,Spring JDBC通过一个模板类org.springframework. jdbc.core.JdbcTemplate封装了样板式的代码,用户通过模板类就可以轻松地完成大部分数据访问的操作。

例如,对于 getMatchCount()方法,仅提供了一个查询 SQL 语句,直接调用模板的queryForInt()方法就可获取查询,用户不用担心获取连接、关闭连接、异常处理等繁琐的事情。

通过JdbcTemplate的支持,可以轻松地实现UserDao的另外两个接口,如代码清单1-6所示。

代码清单1-6 UserDao另外两个方法

package com.smart.dao.jdbc;
…
@Repository
public class UserDao {
… 
      public User findUserByUserName(final String userName) {
String sqlStr = " SELECT user_id,user_name "         ①
               + " FROM t_user WHERE user_name =? ";
 final User user = new User();
 jdbcTemplate.query(sqlStr, new Object[] { userName },
new RowCallbackHandler() {②
   public void processRow(ResultSet rs) throws SQLException {**
     user.setUserId(rs.getInt("user_id"));
     user.setUserName(userName);
    }
   });
 return user;
}
public void updateLoginInfo(User user) {
 String sqlStr = "  UPDATE t_user SET last_visit=?,last_ip=? "
      + "  WHERE user_id =? ";
 jdbcTemplate.update(sqlStr, new Object[] { user.getLastVisit(),
      user.getLastIp(),user.getUserId()});
}
}

findUserByUserName()方法稍微有点复杂。这里使用到了JdbcTemplate#query()方法,该方法的签名为query(String sql,Object[] args, RowCallbackHandler rch),它有3个入参。

sqlStr:查询的SQL语句,允许使用带“?”的参数占位符。
args:SQL语句中占位符对应的参数数组。
RowCallbackHandler:查询结果的处理回调接口,该回调接口有一个方法processRow (ResultSet rs),负责将查询的结果从ResultSet装载到类似于领域对象的对象实例中。
在②处,findUserByUserName()通过匿名内部类的方式定义了一个RowCallbackHandler回调接口实例,将ResultSet转换为User对象。

updateLoginInfo()方法比较简单,主要通过JdbcTemplate#update(String sql,Object[])进行数据的更新操作。

实战经验

在编写SQL语句时,由于SQL语句比较长,一般会采用多行字符串的方式进行构造,如代码清单1-6的①处所示。在编写多行SQL语句时,由于上下行最终会组成一行完整的SQL语句,这种拼接方式很容易产生错误的SQL组合语句:假设在①处第一行的user_name后不加空格,第二行的FROM之前也无空格,组合的SQL将为“... user_nameFROM ...”,由于FROM保留字和user_name连在一起,就产生了非法的SQL语句。以下是一个值得推荐的编程习惯:在每一行SQL语句的句前和句尾都加一个空格,这样就可以避免分行SQL语句组合后的错误。

1.4.3 LoginLogDao
LoginLogDao负责记录用户的登录日志,它仅有一个insertLoginLog()接口方法,与UserDao相似,其实现类也通过JdbcTemplate#update(String sql ,Object[] args)方法完成插入登录日志的操作,如代码清单1-7所示。

代码清单1-7 LoginLogDao

package com.smart.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.smart.domain.LoginLog;
@Repository
public class LoginLogDao {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  public void insertLoginLog(LoginLog loginLog) {
   String sqlStr = "INSERT INTO t_login_log(user_id,ip,login_datetime) "
      + "VALUES(?,?,?)";
   Object[] args = { loginLog.getUserId(), loginLog.getIp(),loginLog.getLoginDate() };
   jdbcTemplate.update(sqlStr, args);
  }
}

1.4.4 在Spring中装配DAO
在编写DAO接口的实现类时,大家也许有一个问题:在以上两个DAO实现类中都没有打开/释放Connection的代码,DAO类究竟如何访问数据库呢?前面说过,样板式的操作都被JdbcTemplate封装起来了,JdbcTemplate本身需要一个DataSource,这样它就可以根据需要从DataSource中获取或返回连接。UserDao和LoginLog都提供了一个带@Autowired注解的JdbcTemplate变量。所以必须事先声明一个数据源,然后定义一个JdbcTemplate Bean,通过Spring的容器上下文自动绑定机制进行Bean的注入。

在项目工程的srcmainresources目录下创建一个名为applicationContext.xml的Spring配置文件,配置文件的基本结构如下所示。

<?xml version="1.0" encoding="UTF-8" ?>
_<!-- 引用Spring的多个Schema空间的格式定义文件 -->_
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xmlns:p="http://www.springframework.org/schema/p"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    …
</beans>

在IDEA中,刷新工程目录树,在srcmainresources文件夹下即可看到该配置文件。双击applicationContext.xml文件,添加如代码清单1-8所示的配置信息。

代码清单1-8 DAO Bean的配置

…
<beans …>
<!-- ①扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
    <context:component-scan base-package="com.smart.dao"/>

<!--②定义一个使用DBCP实现的数据源-->_
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
   destroy-method="close" 
   p:driverClassName="com.mysql.jdbc.Driver"
   p:url="jdbc:mysql://localhost:3309/sampledb" 
   p:username="root"
   p:password="1234" />
<!--③定义JDBC模板Bean  -->
  <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
     p:dataSource-ref="dataSource" />
</beans>

在①处,我们使用Spring的扫描指定类包下的所有类,这样在类中定义的Spring注解(如@Repository、@Autowired等)才能产生作用。

在②处,我们使用Jakarta的DBCP开源数据源实现方案定义了一个数据源,数据库驱动器类为com.mysql.jdbc.Driver,由于我们设置的MySQL数据库的服务端口为3309,而非默认的3306,所以数据库URL中显式指定了3309端口的信息。

在③处配置了JdbcTemplate Bean,将②处声明的dataSource注入JdbcTemplate中,而这个JdbcTemplate Bean将通过@Autowired自动注入LoginLog和UserDao 的Bean中,可见Spring可以很好地将注解配置和XML配置统一起来。

这样,我们就完成了登录模块持久层所有的开发工作,接下来将着手业务层的开发和配置工作,我们将对业务层的业务类方法进行单元测试,到时就可以看到DAO的实际运行效果了,现在暂时把这两个DAO放在一边。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
8月前
|
SQL Java 关系型数据库
解读SpringBoot整合持久层技术之搭建并整合Spring Data JPA项目实战
接下来这阵子就SpringBoot整合持久层技术进行一个项目的搭建,做一个练手,以前搞过但是都淡忘了,特意来总结一下。今天是JPA,后面陆续搭建MyBatis、JdbcTemplate等持久层技术,供大家相互学习。
86 0
|
8月前
|
Java 数据库连接 数据库
Spring Boot之Mybatis Plus:简化持久层开发的利器
本篇详细介绍了如何在Spring Boot应用中使用MyBatis Plus,一个用于简化持久层开发的工具。读者可以轻松实现CRUD操作,从而提高开发效率。展示了使用MyBatis Plus进行数据库操作的具体步骤。
470 5
Spring Boot之Mybatis Plus:简化持久层开发的利器
|
存储 SQL Java
Spring Data开发手册|手摸手教你简化持久层开发工作
Spring Data,是为数据访问提供熟悉且一致的基于Spring的编程模型,同时仍然保留底层数据存储的特殊特性。它是对于数据访问技术,关系数据库和非关系数据库,map-reduce框...
153 0
|
SQL Java 开发者
Spring Data JPA 持久层开发
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bitree1/article/details/50014705 下面总结一...
939 0
|
27天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
40 0
|
2月前
|
缓存 Java Maven
Spring Boot自动配置原理
Spring Boot自动配置原理
48 0
|
1月前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
111 0
|
1月前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
15天前
|
前端开发 Java 应用服务中间件
Springboot对MVC、tomcat扩展配置
Springboot对MVC、tomcat扩展配置
|
6天前
|
安全 Java 应用服务中间件
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置
23 0
江帅帅:Spring Boot 底层级探索系列 03 - 简单配置