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

本节书摘来自异步社区《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放在一边。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值