1. 案例简单描述
本例实现一个简单的登录验证模块,登录页面提供一个带用户名/密码的输入表单,用户填写并提交,服务器端检查是否匹配,匹配成功则进入主页面,记录登录日志,信用+5分,否则提示登录失败。
2.环境准备
案例开发环境:MyEclipse2015+Spring 4.x +SQL Server2008
2.1 数据库
USE [sampledb]
GO
CREATE TABLE [dbo].[t_user](
[user_id] [int] IDENTITY(1,1) NOT NULL,
[user_name] [nvarchar](50) NULL,
[credits] [int] NULL,
[password] [nvarchar](50) NULL,
[last_visit] [datetime] NULL,
[last_ip] [nvarchar](50) NULL
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[t_login_log](
[login_log_id] [int] IDENTITY(1,1) NOT NULL,
[user_id] [int] NULL,
[ip] [nvarchar](50) NULL,
[login_datetime] [datetime] NULL
) ON [PRIMARY]
GO
一共两张表,用户信息t_user和登录历史记录t_login_log
2.2 类包划分
类包以分层的方式进行组织,共划分为4个包,领域对象严格意义上讲属于业务包,但是由于领域对象可能被持久层和展现层共享,所有一般将其单独划分到一个包中。如图所示:
单元测试的包和程序的类包对应<我这里写了是servicetest.感觉没啥影响,可能是单元测试这样方便知道测试哪个类?具体后面看测试的时候再看了先丢个黑人问号了..??>,但是放在不同的文件夹下,本示例仅对业务层的业务类进行单元测试。
3.持久层
持久层负责数据的访问和操作,DAO类被上层的业务类调用,这里选用JDBC作为持久层的实现技术。
3.1 建立领域对象
领域对象(Domain Object)也成为实体类,它代表业务的状态,一般来说,它属于业务层,但是它贯穿展现层、业务层和持久层,并最终被持久化到数据库中,领域对象不一定等同于数据库表,不过对于简单的应用来说,往往领域对象都拥有对应的数据库表。
持久层的主要工作是从数据库表中加载数据并实例化领域对象,或将领域对象持久化到数据表中,由于持久层需要用到领域对象,所以我们将本属于业务层的领域对象提前到持久层来。下述共两个领域对象User和LoginLog,分别对应数据库表中的t_user和t_login_log
1. 用户对象领域
package com.baobaotao.domain;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable{
private int userId;
private String userName;
private String password;
private int credits;
private String lastIp;
private Date lastVisit;
public Date getLastVisit() {
return (Date) lastVisit.clone();
}
}
此处把一些get和set方法省略了,但是有一点注意要注意的是对Date对象进行判断时需要用到clone,书上没有写,这里给加上了~~实现Serializable接口,可以方便序列化,书上注释为<
领域对象一般要实现此接口,方便序列化>,同理,另一个领域对象为:
2.登录日志领域
package com.baobaotao.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;
}
3.UserDao
@Repository //注解:通过Spring注解定义一个DAO
public class UserDao {
@Autowired //自动注入JdbcTemplate
private JdbcTemplate jdbcTemplate;
/**
* 根据用户名或密码获取匹配的用户数量
* @param userName
* @param password
* @return 等于1表示正确,0表示错误
*/
public int getMatchCount(String userName, String password) {
String sqlStr = " SELECT count(*) FROM t_user "
+ " WHERE user_name =? and password=? ";
return jdbcTemplate.queryForObject(sqlStr,new Object[]{userName,password},Integer.class);
//在Spring4.x中取消了jdbcTemplate.queryForInt(..)方法,改用jdbcTemplate.queryForObject
}
public User findUserByUserName(final String userName) {
String sqlStr = " SELECT user_id,user_name,credits "
+ " FROM t_user WHERE user_name =? ";
final User user = new User();
jdbcTemplate.query(sqlStr, new Object[] { userName },
<span style="font-family:SimSun;">//匿名内部类实现回调</span>
<span style="font-family:SimSun;"> </span> new RowCallbackHandler() {
public void processRow(ResultSet rs) throws SQLException {
user.setUserId(rs.getInt("user_id"));
user.setUserName(userName);
user.setCredits(rs.getInt("credits"));
}
});
return user;
}
public void updateLoginInfo(User user) {
String sqlStr = " UPDATE t_user SET last_visit=?,last_ip=?,credits=? "
+ " WHERE user_id =?";
jdbcTemplate.update(sqlStr, new Object[] { user.getLastVisit(),
user.getLastIp(),user.getCredits(),user.getUserId()});
}
}
需要注意的点有:
- 一定要添加注解!!!否则后续配置扫描识别不出来
在Spring4.x中取消了jdbcTemplate.queryForInt(..)方法,改用jdbcTemplate.queryForObject
- SQL语句较长的时候通常换行,但是换行时一定要注意前后不能连起来如:"select ...."+"from..",这样from就会和select中的内容连起来,导致数据库语句错误,常用避免错误方式为在每每一行SQL语句的句前和句尾都加一个空格。
- RowCallbackHandler:查询结果的处理回调接口,该接口有一个processRow(ResultSet rs),负责把查询的结果集从ResultSet装载到类似领域对象的对象实例中
4. LoginLogDao
@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);
}
}
注意事项同上。
4. 业务层
此例中只有一个业务类,UserService,负责将持久层的UserDao和LogLoginDao组织起来完成认证、登录日志记录等操作。
代码如下:
@Service //将UserService标注为一个服务层的Bean
public class UserService {
@Autowired //注入userDao和loginLogDao两个DAO层的bean
private UserDao userDao;
@Autowired
private LoginLogDao loginLogDao;
public boolean hasMatchUser(String userName, String password) {
int matchCount =userDao.getMatchCount(userName, password);
return matchCount > 0;
}
public User findUserByUserName(String userName) {
return userDao.findUserByUserName(userName);
}
public void loginSuccess(User user) {
user.setCredits( 5 + user.getCredits());
LoginLog loginLog = new LoginLog();
loginLog.setUserId(user.getUserId());
loginLog.setIp(user.getLastIp());
loginLog.setLoginDate(user.getLastVisit());
userDao.updateLoginInfo(user);
loginLogDao.insertLoginLog(loginLog);
}
}
5. 在Spring中装配DAO和Service
5.1 装配DAO
在上述的两个DAO的实现中都没有打开或者释放Connection的代码,那DAO怎么去访问数据库呢?其实是样板式的操作都被JdbcTemplate封装起来了,JdbcTemplate本身需要一个DataSource,这样它就可以从DataSource中获取或返回连接,UserDao和ServiceDao都提供了一个带@Autowired注解的JdbcTemplate变量,所以我们首先必须事先声明一个数据源,然后定义一个JdbcTemplate Bean,通过Spring容器的上下文自动绑定机制进行Bean的注入。
添加引用Spring的多个Schema空间格式定义文件了..
<!-- 1.扫描类包,将标注Spring注解的类自动转化为Bean,同时完成Bean的注入 -->
<context:component-scan base-package="/com.baobaotao.dao"></context:component-scan>
<!-- 2.定义一个使用JDBC实现的数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.microsoft.sqlserver.jdbc.SQLServerDriver" />
<property name="url"
value="jdbc:sqlserver://localhost:1433;database=sampledb;integratedSecurity=false" />
<property name="username" value="sa" />
<property name="password" value="..." /></bean>
<!-- 3. 定义jdbc模板Bean -->
<bean id="jdbcTem" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />
DAO Bean的配置大概就是以上3步了。其中
1,使用Spring的<context:component-scan>扫描指定类包下所有的类,这样在类中定义的Spring注解(如@Repository、@Autowired等)才能起作用。
2. 定义数据源,此处要写清楚自己数据库的连接字符串,包括数据库名称、登录用户名和密码,此处为SQL server,数据库:sampledb
3. 配置JdbcTemplate Bean,把2中生命的DataSource导入到JdbcTemplate中。
5.2 装配Service
事务管理的代码虽然不用出现在程序中,但我们必须以某种方式告诉Spring哪些业务类需要工作于事务环境下以及事务的规则内容等,一遍Spring根据这些信息自动为目标业务类添加事务管理功能。
还是先引入aop以及tx命名空间对应的schema文件,这样就可以使用aop和tx空间下的配置了
然后:
<!-- a1扫描service包,应用spring注解 -->
<context:component-scan base-package="/com.baobaotao.service"></context:component-scan>
<!-- a2.配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />
<!-- a3.通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod"
expression=" execution(* com.baobaotao.service..*(..))" />
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
其中a2处扫描类包,以便service类中的Spring注解生效,a2定义一个机遇DataSourceTransactionManager的事务管理器,该管理器负责声明式事务的管理,需要引用DataSource Bean
这样,关于applicationContext.xml的配置就完成了。
6.单元测试
File->New ->Other ..->Java ->JUnit->JUnit Test Case
package com.baobaotao.service;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.baobaotao.domain.User;
import com.baobaotao.service.UserService;
@RunWith(SpringJUnit4ClassRunner.class) //基于Junit4的Spring框架
@ContextConfiguration(locations={"/applicationContext.xml"}) //启动Spring容器
public class TestUserService {
//3.注入Spring容器中的Bean
@Autowired
private UserService userService;
@Test
public void test() {
Boolean b1 = userService.hasMatchUser("JY", "123");
Boolean b2 = userService.hasMatchUser("ST", "1234");
assertTrue(b1);
assertTrue(!b2);
System.out.println(b1);
}
@Test
public void findUserByUserName(){
User user = userService.findUserByUserName("JY");
assertEquals(user.getUserName(), "JY");
}
@Test
public void addLog(){
User u = new User();
u.setUserName("JY");
u.setPassword("123");
userService.loginSuccess(u);
}
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.>>>>>>
上面的代码实现倒是很简单,下面是错误的记录,
7. 错误记录
1.配置时报错,The prefix "context" for element"context:component-scan" is not bound.
原因是,配置标签少了东西,点击上面错误可以看到连接
2. 单元测试时出问题:method initializationerror not found ,原因是单元测试包引用不全,具体连接见:
http://blog.csdn.net/chenleixing/article/details/44257839点击打开链接
3.
Caused by:org.springframework.beans.factory.BeanCreationException:Error creating beanwith name 'loginLogDao' defined in file[G:\myeclipse_workspace\LoginSpring\WebRoot\WEB-INF\classes\com\baobaotao\dao\LoginLogDao.class]:BeanPostProcessor before instantiation of bean failed; nested exception isorg.springframework.beans.factory.BeanCreationException: Error creating beanwith name'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0':Initialization of bean failed; nested exception isjava.lang.NoClassDefFoundError:org/aspectj/weaver/reflect/ReflectionWorld$ReflectionWorldException
此处截取了部分片段,可以看到关键词,这里提示路径为[G:\myeclipse_workspace\LoginSpring\WebRoot\WEB-INF\classes\com\baobaotao\dao\LoginLogDao.class],但实际上应该在src\com\baobaotao\dao\LoginLogDao.class,看到上面配置时写的是<context:component-scan base-package="/com.baobaotao.dao"/>,查了资料后发现其实就是应该这么写,但是我的路径就是不对,最后发现是一个包的问题,
Aspectjtweaver.jar包,这个是配置aop必须的,引入这个包之后便正常了。
4.
配置时缺少空格
缺少空格:
<aop:pointcutid="serviceMethod"
expression="execution(* com.baobaotao.service..*(..))" /> 此execution(* com.baobaotao.service..*(..))* 和com之间要有空格!!!
第一次配置的时候就出现了那么多问题,当时真的想哭的心就有了...辛辛苦苦按书上代码来的,醉了,还好都解决了~~继续努力。