[手把手教程][JavaWeb]SSM框架验证、修复和实例
手把手验证上期的SSM项目:优雅的SpringMvc+Mybatis整合之路框架。
- 说实话,这一期也应该算是上一期的,但是这起需要单独拿出来的原因有几个,最重要的是我个人讨厌长篇大论。(最简单的就是直接丢项目)
- 但是,直接丢出来,未免太过粗糙,也不能体验出我们编程的思维养成,故此一步一步的走出来。
- 对于很多哥们催更的事情,这个确实也不是那么容易出来的,前面的框架搭建是从网上找的资料一步一步的组合的,但是实际使用还是有很多问题,本篇文章将会解决这些问题。
- 同时,兄弟们稍微耐心点,我这边也是一点点实现,还请客官们放下手中的西瓜刀。
- 再次安利一波,博客地址:acheng1314.cn
工具
- IDE为idea15
- JDK环境为1.8
- maven版本为maven3
- Mysql版本为5.5.27
- Tomcat版本为7.0.52
本期目标
- 项目框架介绍和冲突解决。
- 实例Service和Dao。项目框架测试。
- 登录、注册业务的分析、创建和演示。
冲突解决
为什么我把冲突解决放在第一位?
因为在冲突解决后,我们可以进入测试阶段,同时在解决冲突的时候,我们会一点点的体会到SSM项目框架中各个框架之间是怎么协同工作的,也就可以完成项目介绍。
重要的是,在实际生活中,我们去公司上班,一般也是先解决项目框架冲突问题。
-
打开项目后,随意选择一个Spring的配置文件,会弹出如下界面:
SpringMVC详细运行流程图总结:目前项目中的静态资源我们已经处理完毕,加入了一部分Web的静态资源,增加了Spring的web处理配置,修改了web.xml,log4j2替换为LogBack,Junit版本提升为4.12,加入了一个BaseMapper.xml。
web.xml文件如下:
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> <!-- 如果是用mvn命令生成的xml,需要修改servlet版本为3.1 --> <!-- 配置DispatcherServlet --> <servlet> <display-name>SSM</display-name> <servlet-name>mvc-dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置springMVC需要加载的配置文件 spring-dao.xml,spring-service.xml,spring-web.xml Mybatis - > spring -> springmvc --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-*.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>mvc-dispatcher</servlet-name> <!-- 默认匹配所有的请求 --> <url-pattern>/</url-pattern> <!--<url-pattern>/css/*</url-pattern>--> <!--<url-pattern>/images/*</url-pattern>--> <!--<url-pattern>/fonts/*</url-pattern>--> </servlet-mapping> <!-- spring框架提供的字符集过滤器 --> <!-- spring Web MVC框架提供了org.springframework.web.filter.CharacterEncodingFilter用于解决POST方式造成的中文乱码问题 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <!-- force强制,促使 --> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!--druid ==> WEB方式监控配置--> <servlet> <servlet-name>DruidStatView</servlet-name> <servlet-class>com.alibaba.druid.support.http.StatViewServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DruidStatView</servlet-name> <url-pattern>/druid/*</url-pattern> </servlet-mapping> <filter> <filter-name>druidWebStatFilter</filter-name> <filter-class>com.alibaba.druid.support.http.WebStatFilter</filter-class> <init-param> <param-name>exclusions</param-name> <param-value>/public/*,*.js,*.css,/druid*,*.jsp,*.swf</param-value> </init-param> <init-param> <param-name>principalSessionName</param-name> <param-value>sessionInfo</param-value> </init-param> <init-param> <param-name>profileEnable</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>druidWebStatFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <error-page> <error-code>404</error-code> <location>/static/view/404.html</location> </error-page> </web-app>
BaseMapper.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"> <!-- namespace指向我们的dao所在的地址 --> <mapper namespace="cn.acheng1314.dao"> </mapper>
其他的文件修改的信息都可以在上面看到就不再次贴出来了。
SSM框架实例--仓库管理系统--登录注册的功能分析
分析:
- 登录和注册,都是用户的行为,也是我们开发中常说的某个行为对应的业务。
- 注册用户,意味着添加用户,添加了用户后,我们才有登录功能的继续执行的可能。
- 用户登录,也是用户信息的查找和比对过程。一般来说用户先把信息提交给程序,然后程序按照流程执行后提交数据给服务器(这个流程中程序可能会产生一些业务逻辑的判断),服务器收到数据并进行数据对比,当用户信息存在且帐号和密码匹配才能登录成功,否则前面的任意条件不满足即为登录失败。
- 我们可以依靠单一的用户表来实现用户的登录数据获取和注册信息的添加。
功能大概分析完毕后,我们就可以考虑下如何实现这些功能。大概图示如下:
ssm框架经典三层分析- 首先用户信息需要存储,我们需要数据库的支撑。
- 有了数据库的支援后,我们需要先测试用户信息的添加和查找。就是我们常说的Dao层。
- 当我们的Dao层拿到想要的数据,我们需要Service层将Dao层的操作作为服务提供给控制器,再由控制器提供给前台页面。
- 同样的用户需要获取某个数据,先是浏览器获取到用户请求→web层→Service层→Dao层,再接着重复上面的操作。
上面一张图用模型已经很好的说明了java web后端各层的关系,下面一张图是经典三层对照MVC的描述。
ssm框架经典三层分析对照Mvc上面的图引用自互联网资料,下面的黑色字是我添加上去的。
-
上面图中,我们可以看到一部分MVC设计和图中标记的类似,但是这只是一种MVC模式的栗子。
-
关于更加详细的MVC,我们可以百度百科查看。
上面提到的东西,我们适可而止就行了,很多东西现在还不是深究的时候,我们要先懂一个事物的外在表现,才有进一步的探索,所以我们需要等缘分的到来。
SSM框架实例--仓库管理系统--注册、登录的功能模拟实现
按照前面我们的分析来讲,我们需要先考虑Dao层的实现,毕竟有了Dao层对数据的封装,才可以有后面的操作,所以后面我的栗子中,一切都是按照Dao→Service→Controller→View这样的一个步骤来实现。
- 用户注册功能的Dao层实现。
-
数据库基本操作为:增删改查。
-
我们根据数据库常规操作的类型,分别对其进行抽象,所以产生了Dao.java这个接口对象的基本模型。代码如下:
/** * 通过接口编程 * @param <T> 泛型用于解耦,同时避免写重复代码 */ public interface Dao<T> { /** * 添加某个对象 * @param t 待添加的对象 * @return 返回受影响的行数 */ int add(T t); /** * 删除某个对象,在企业开发中,我们一般不做物理删除,只是添加某个字段对其数据进行可用控制 * @param t 待删除对象 * @return 返回受影响的条数 */ int del(T t); /** * 更新某个对象 * @param t 待更新对象 * @return 返回受影响的条数 */ int update(T t); /** * 通过ID查找一个对象 * @param Id 待查询的对象的ID * @return 返回该ID对应的对象 */ T findOneById(Serializable Id); /** * 查找对象集合 * @return 返回对象集合 */ List<T> findAll(); }
-
实现用户的Dao层:
- 我们要针对用户写用户的Dao层,我们需要有一个用户对象的封装,直接在domain包下面创建User.java,代码如下:
/*** 创建数据库用户表: CREATE TABLE `user` ( `login_id` varchar(20) NOT NULL COMMENT '登陆ID', `pwd` varchar(20) NOT NULL COMMENT '用户密码', `name` varchar(100) NOT NULL COMMENT '用户姓名', `age` int(3) NOT NULL COMMENT '用户年龄', `sex` varchar(3) NOT NULL COMMENT '性别', `duty` varchar(15) COMMENT '职务', `cell_number` varchar(15) COMMENT '手机号', `photo_url` varchar(75) COMMENT '头像地址', `used` boolean NOT NULL COMMENT '账号是否可用', PRIMARY KEY (`login_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT='用户表'; 插入默认数据: INSERT INTO `user` (`login_id`,`pwd`,`name`,`age`,`sex`,`used`) VALUES ('pc859107393','123456','阿程',20,'男',true), ('pc228568859','123456','chengcheng',20,'女',true), ('pangpang','123456','余下一整夜',25,'男',true), ('111111','123456','手拉手系列教程',22,'女',true); */ public class User implements Serializable { private String name; //名字 private String sex; //性别 private String loginId; //登陆ID private String pwd; //密码 private String duty; //职务 private int age; //年龄 private String cellNumber; //手机号 private String photoUrl; //头像地址 private boolean used = true; //是否可用,默认值是true ···省略get、set和toString } //推荐大家把每个bean对应的数据库操作备注到上面。 //打开Navicat For Mysql,链接上数据库,创建对应的数据库,并运行上面的sql产生数据库表和初始记录。
- 当我们数据库创建完毕后,我们需要写一个接口对user表进行操作封装。我们在dao包下面创建UserDao接口,代码如下:
/** * 在传统的jdbc操作中,我们需要手动管理数据库连接的开关,数据库资源访问的开关等等 * <br/>但是,我们这里采用了Mybatis和Druid这两个框架,那么我们可以完全不必理会数据库连接等等的控制, * <br/>我们只需要更加专注于业务实现的开发。 */ public interface UserDao extends Dao<User> { int add(User user); int del(User user); int update(User user); User findOneById(Serializable Id); List<User> findAll(); }
- 完成UserDao的封装后,传统的操作这一步需要自己手动实现UserDao的Impl,并实现对数据库的操作等等。而我们使用Mybatis后,UserDao的Impl在Mybatis的mapper文件夹中指定为xml,我们的Dao文件除了数据库操作的语句其他的都无需关注,那么剩下的数据库操作什么的我们都无需关心,毕竟Mybatis和druid都把其他的事情帮我们做了。UserDao.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"> <!-- namespace指定我们的到具体的bean --> <mapper namespace="cn.acheng1314.dao.UserDao"> <!--查找--> <select id="findOneById" resultType="User" parameterType="String"> SELECT * FROM `user` WHERE login_id = #{userId} <select> <!--增加语句--> <insert id="add" parameterType="User"> INSERT INTO `user` (`login_id`,`name`,`age`,`sex`,`duty`,`cell_number`,`photo_url`,`pwd`,`used`) VALUES (#{loginId},#{name},#{age},#{sex},#{duty},#{cellNumber},#{photoUrl},#{pwd},#{used}) </insert> <!-- 删除 --> <update id="del" parameterType="User"> UPDATE `user` SET `used`=FALSE WHERE `login_id`=#{loginId}; </update> <!-- 更新用户信息 --> <update id="update" parameterType="User"> UPDATE `user` SET `name`=#{name}, `age`=#{age}, `sex`=#{sex}, `duty`=#{duty}, `cell_number`=#{cellNumber}, `photo_url`=#{photoUrl} WHERE `login_id`=#{loginId}; </update> </mapper>
- mapper下面的UserDao.xml已经完成,这就意味着我们的UserDao基本完成,那么我们应该来一次单元测试来看看效果如何。UserDaoTest.java如下:
@RunWith(SpringJUnit4ClassRunner.class) //spring的单元测试 @ContextConfiguration({"classpath:spring/spring-*.xml"}) //上下文配置 public class UserDaoTest { @Autowired private UserDao userDao; //初始化Dao层,面向接口编程 /** * 添加用户的单元测试,添加成功与否会有对应的提示。 * 当然按照我这个配置一般会正确,如果说出错就需要你一步一步的看错误的提示代码了。 * 添加同样的LoginId的用户会添加失败,因为在上面把LoginId作为了数据库表的主键。 */ @Test public void testAdd() { User user = new User(); user.setLoginId("pc147852369"); user.setName("雨下一整夜"); user.setPwd("123456"); user.setSex("未知"); int result = 0; //受影响的行数默认为0 try { result = userDao.add(user); } catch (Exception e) { e.printStackTrace(); System.out.println("添加用户失败"); } if (result>0) System.out.println("添加用户成功"); } /** * 查找用户测试,成功与否会有log输出 */ @Test public void testFindOneId() throws Exception { User user = new User(); user.setLoginId("pc147852369"); User result = null; //受影响的行数默认为0 try { result = userDao.findOneById(user.getLoginId()); } catch (Exception e) { e.printStackTrace(); System.out.println("查找用户失败"); } if (null!=result) System.out.println("查找用户成功\n"+result.toString()); } @Test public void testDel() { User user = new User(); user.setLoginId("pc147852369"); int result = 0; //受影响的行数默认为0 try { result = userDao.del(user); } catch (Exception e) { e.printStackTrace(); System.out.println("删除用户失败"); } if (result>0) System.out.println("删除用户成功"); } @Test public void testUpdate() { User user = new User(); user.setLoginId("pc147852369"); user.setName("手把手教程"); user.setPwd("123456"); user.setSex("男"); int result = 0; //受影响的行数默认为0 try { result = userDao.update(user); } catch (Exception e) { e.printStackTrace(); System.out.println("更新用户信息用户失败"); } if (result>0) System.out.println("更新用户信息用户成功"); } }
数据的操作结果我们都可以在mysql中查看,这里就不一一截图了。当然我们也可看到每次单元测试都需要在类上面注解spring单元测试和spring的上下文,按照我们编程的原则,一次编码处处运行,我们可以把这些注解放到BaseTest.java中,后面所有的单元测试都继承BaseTest.java即可避免大量重复编码。
-
UserService实现(注意编程思维的养成)
- 根据我们面向接口编程的思维来讲,我们在Service中核心是实现Dao层,并调用Dao层。
- 刚才我们单元测试,我们的UserDao层通过测试了,我们现在中心就应该放在业务逻辑的实现,而不是继续纠缠Dao层,毕竟数据持久化已经实现了。
- 从服务端程序的角度看来,用户的主要业务有注册、登录、注销登录、注销帐号等等,这里我们先拿注册来说事。
-
用户注册流程分析(用户角度):
- 填写帐号相关信息
- 提交注册信息
- 服务器返回是否注册成功
-
用户注册流程分析(服务器角度):
- 收到用户注册请求
- 解包数据→封装到UserBean
- 解包数据失败(请求信息异常),返回错误提示信息
- 针对具体的用户信息检查是否符合标准
- 不符合检查标准,返回对应的错误提示
- 通过检查,调用Dao检查是否存在同样的用户
- 数据库已经存在相同的用户信息,不能重复添加,返回错误提示信息
- 不存在同样的用户,添加新用户,并返回成功的提示信息
流程图反映如下:
ssm框架用户行为解析流程图- 代码实现用户注册的Service:
//创建一个BaseService接口,用泛型解耦 public interface UserService extends BaseService<User> { //添加用户的实例 void add(User user) throws Exception; } ------------------------------分割线---------------------------------- //创建一个UserService继承BaseService,并指定具体的实体类型 //为什么要再写一个UserService接口?不同对象的业务体系不同,BaseService并不能完全替代不同对象的具体行为表现 public interface UserService extends BaseService<User> { void add(User user) throws Exception; } ------------------------------分割线---------------------------------- //创建UserServiceImpl实现UserService接口 @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; /** * 添加用户,一般来说需要检查用户为空、用户名为空、密码为空 */ public void add(User user) throws UserCanNotBeNullException, UserNameCanNotBeNullException, UserPwdCanNotBeNullException, UserAireadyExistException, OtherThingsException { //先检查用户是否存在 if (null == user) { //抛出用户为空的自定义异常 throw new UserCanNotBeNullException("User can not be Null"); } //用户名不能为空检查 if (StringUtils.isEmpty(user.getLoginId())) { //抛出用户名为空的自定义异常 throw new UserNameCanNotBeNullException("User name can not be Null"); } //用户密码不能为空检查 if (StringUtils.isEmpty(user.getPwd())) { //抛出用户密码为空的自定义异常 throw new UserPwdCanNotBeNullException("User name can not be Null"); } //由于我这个是仓库管理系统,根据业务需求来说,我们的用户基本信息都是不能为空的 //基本信息包括:姓名、年龄、用户名、密码、性别、手机号,年龄大于18 if (StringUtils.isEmpty(user.getDuty()) || StringUtils.isEmpty(user.getSex()) || user.getAge() > 18 || StringUtils.isEmpty(user.getCellNumber())) { //其他综合异常 throw new OtherThingsException("Some use's base info can not be null"); } //已经存在相同用户 if (null != userDao.findOneById(user.getLoginId())) { //存在相同的用户异常 throw new UserAireadyExistException("Register User Failed,Because the user Aiready exist"); } int result = 0; //受影响的行数默认为0 try { result = userDao.add(user); } catch (Exception e) { System.out.println("添加用户失败,用户已经存在"); //其他用户添加失败异常 throw new OtherThingsException(e); } if (result > 0) System.out.println("添加用户成功"); } //···省略的其他方法··· }
- 老规矩,写完每个Service后,都需要针对具体的对象的行为进行单元测试,UserServiceTest.java代码如下:
public class UserServiceTest extends BaseTest { @Autowired private UserServiceImpl userService; //此处直接使用UserService的实现类,主要是方便抛出异常,然后异常出现时候可以针对性的处理 @Test public void testAdd() { User user = new User(); try { userService.add(user); } catch (UserCanNotBeNullException e) { //用户不能为空异常抛出 e.printStackTrace(); } catch (UserNameCanNotBeNullException e) { //用户名不能为空 e.printStackTrace(); } catch (UserPwdCanNotBeNullException e) { //用户密码不能为空 e.printStackTrace(); } catch (UserAireadyExistException e) { //用户存在抛出 e.printStackTrace(); } catch (OtherThingsException e) { //其他综合异常或是不能处理的异常 e.printStackTrace(); } } //···省略的其他测试代码··· }
- 同样的,我们的Service的测试代码执行后,我们可以在mysql中看到具体的数据变化,也不再一一贴图了。
-
因为篇幅和时间原因,我们暂时就不写具体功能的详细实现了,毕竟学习不是一朝一夕,多培养思维才是重要的事情。从这一期开始代码逐渐增多,毕竟学习过程就是这样的递增的,等熟悉以后可能很多地方就更多是培养思维方式,代码就直接贴上(详细的注释不会少)。
这一期总结:
- 解决项目框架冲突解决,各个框架职能的简单介绍。
- 简单的原理解释。
- java web经典mvc、三层架构简单介绍。
- 部分简单的SQL语句。
- 针对Dao层和Service层的单元测试。
- 简单业务实现的流程分析。
下期预告:我们实现详细的功能和Controller(Url请求分发),同时分出API接口为以后打下基础。
项目地址:点击访问github
网友评论
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource' defined in file [E:\IdeaProjects\SSM_LOG\target\classes\spring\spring-dao.xml]: Invocation of init method failed; nested exception is com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
<error-code>404</error-code>
<Location>/static/view/404.html</Location>
</error-page>
当中 的Location应该是location,首字母小写
“BaseService<User>”这个是继承的这个在哪里我怎么没找到
BaseTest这个类...刚刚忘记了
java.sql.SQLException: com.mysql.jdbc.Driver //æ°æ®åºè¿æ¥ç驱å¨
请问是什么问题?大神
@Autowired
private UserDao userDao; //初始化Dao层,面向接口编程
...
}
userDao 显示Could not autowire, No beans of 'UserDao' type found 。
这是什么问题啊?
```
<!-- 4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.xxxx.dao"/>
</bean>
```
UserDao.java 是在com.xxx.dao包下面,为什么还没有注入呢?
<error-page>
<error-code>404</error-code>
<location>/static/view/404.html</location>
</error-page>
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSource': Error setting property values; nested exception is org.springframework.beans.PropertyBatchUpdateException; nested PropertyAccessExceptions (1) are:
PropertyAccessException 1: org.springframework.beans.MethodInvocationException: Property 'databaseType' threw exception; nested exception is java.lang.IllegalStateException: Driver for test database type [HSQL] is not available in the classpath
com.mysql.jdbc.Driver
这两个驱动
第二个<select>改成</select>
大哥,我能跟你混java web开发吗