根据个人学习情况,筛选出特别关注的信息,完整内容请看原版,仅仅是个人学习笔记。
目录
目录
2-5 [重点] 在Spring中装配DAO层、Service业务层(使注解的类自动转化为Bean,同时完成注入)
2-11 使用jetty运行web运用,当然也可以在IDE工具中配置Web应用服务器,比如tomcat
3-1、proxy-target-class="true/false"事务配置说明
3-1 java.lang.NoSuchMethodError: org.springframework.core.io.ResourceEditor错误
一、环境准备
1.数据库
建议采用Mysql5.X数据库
MySQL 4.1.0以前的版本不支持事务,4.1.0本身也只对事务提供有限的支持。Spring的各种声明式事务需要底层数据库的支持。
MySQL5.0增加存储过程、视图、游标、触发器、XA事务。
MySQL5.1增加事件调度器、分区、可插拔的存储引擎API、行复制、全局动态查询日志修改。
MySQL5.5默认存储引擎更改为InnoDB,提高了默认线程并发数,后台输入/输出线程控制,主线程输入/输出速率控制,操作系统内存分配程序使用控制,适应性散列索引控制,恢复组提交,多缓冲池实例,半同步复制,中继日志自动恢复,建立快速索引,高效的数据压缩等特性。
MySQL5.6中InnoDB性能加强,InnoDB死锁信息可以记录到错误日志,支持主从延时复制,增强行级复制功能,基于CRC32校验的复制事件等。
-------------示例用到的数据库表创建语法---------------
create database sampled DEFAULT CHARACTER SET utf8;
/* 创建用户信息表 */
create table t_user (
user_id INT AUTO_INCREAMENT PRIMARY KEY,
user_name VARCHAR(30),
credits INT,
password VARCHAR(30),
last_vist datetime,
last_ip varchar(30)
)ENGINE=InnoDB;
/* 创建用户登录日志表 */
create table t_login_log (
login_login_id INT AUTO_INCREAMENT PRIMARY KEY,
user_id INT,
ip VARCHAR(30),
login_datetime datetime
)ENGINE=InnoDB;
2.构建工具Maven
Repository:即放置Artifact的地方,有中央仓库、公共仓库、私有仓库及本地仓库之分。一般情况下,公司或开发者组织都需要部署一个私有仓库,可使用Nexus(http://www.sonatype.org/nexus)创建Maven私有仓库。
3.建立工程(IntelliJ IDEA)
设置源文件和配置文件都使用UTF-8编码格式
File—Settings—File Encodings—UTF-8
分别设置IDE Encoding、Project Encoding、 Default encoding for properties files
二、项目快速搭建
该示例只是简单实现用户登录的验证,成功则跳转至main页面,失败则重定向到loginCheck页面提示错误信息。主要讲解怎么搭建一个简单的Spring 项目
首先,看下项目结构的目录
2-1 根据数据库表结构,在domain创建实体类
直接贴代码,这边可以看到都实现了Serializable接口,以便可以序列化
public class LoginLog implements Serializable { private int loginLogId; private int userId; private String ip; private Date loginDate; 省略get和set.... }
public class User implements Serializable { private int userId; private String userName; private String password; private int credits; private String lastIp; private Date lastVisit; 省略get和set.... }
2-2 pom.xml文件配置
各个配置内容已有注释描述,这里不再一一说明
2-3 创建数据处理的Dao层
package com.javahx.dao; import com.javahx.domain.User; 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 java.sql.ResultSet; import java.sql.SQLException; @Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; private final static String MATCH_COUNT_SQL = " SELECT count(*) FROM t_user " + " WHERE user_name =? and password=? "; private final static String UPDATE_LOGIN_INFO_SQL = " UPDATE t_user SET " + " last_visit=?,last_ip=?,credits=? WHERE user_id =?"; /** * 根据用户输入信息查询用户是否存在 * @param userName 用户名 * @param password 密码 * @return */ public int getMatchCount(String userName, String password) { return jdbcTemplate.queryForObject(MATCH_COUNT_SQL, new Object[]{userName, password}, Integer.class ); } public User findUserByUserName(final String userName) { final User user = new User(); String sqlStr = " SELECT user_id,user_name,credits " + " FROM t_user WHERE user_name =? "; jdbcTemplate.query(sqlStr, new Object[]{userName}, //匿名类方式实现的回调函数 //processRow(ResultSet resultSet)负责将查询的结果从ResultSet装载到类似于领域对象的对象实例中 new RowCallbackHandler() { @Override public void processRow(ResultSet resultSet) throws SQLException { user.setUserId(resultSet.getInt("user_id")); user.setUserName(userName); user.setCredits(resultSet.getInt("credits")); } }); return user; } public void updateLoginInfo(User user) { jdbcTemplate.update(UPDATE_LOGIN_INFO_SQL, new Object[] { user.getLastVisit(), user.getLastIp(),user.getCredits(),user.getUserId()}); } }
2-4 创建业务层Service
package com.javahx.service; import com.javahx.dao.LoginDao; import com.javahx.dao.UserDao; import com.javahx.domain.LoginLog; import com.javahx.domain.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class UserService { @Autowired private UserDao userDao; @Autowired private LoginDao loginDao; 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); } @Transactional 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); loginDao.insertLoginLog(loginLog); } }
2-5 [重点] 在Spring中装配DAO层、Service业务层(使注解的类自动转化为Bean,同时完成注入)
- 通过使用JdbcTemplate,实现了访问数据库的GUID操作。
- Transactional 事务管理
smart-context文件命名随意,如下描述
配置文件的文件名采用<Servlet名>-servlet.xml的形式,如smart-servlet.xml(必须在/WEB-INF目录下创建该文件),中国配置文件无须通过web.xml的contextConfigLocation上下文参数进行声明,因为Spring MVC的Servlet会自动将smart-servlet.xml文件和Spring的其他配置文件(如smart-dao.xml、smart-service.xml)进行拼装。
以下配置都有注解
<?xml version="1.0" encoding="UTF-8" ?> <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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <!-- ①扫描类包,将标准Spring的注解的类自动转换为Bean,同时完成Bean的注入 --> <context:component-scan base-package="com.javahx.dao"/> <!-- 扫描Service类包,应用Spring的注解配置 --> <context:component-scan base-package="com.javahx.service"/> <!-- 配置数据源 --> <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="123456"/> <!-- ②定义JDBC模板的Bean --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource"/> <!-- 配置事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dataSource"/> <!-- 通过AOP配置提供事务增强,让service包下所有Bean的方法拥有事务--> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceMethod" expression="(execution(* com.javahx.service..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod" /> </aop:config> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> </beans>
2-6 单元测试
package com.javahx.service; import com.javahx.domain.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests; import org.testng.annotations.Test; import static org.testng.Assert.*; @ContextConfiguration("classpath*:/smart-context.xml") public class UserServiceTest extends AbstractTransactionalTestNGSpringContextTests { @Autowired private UserService userService; @Test public void testHasMatchUser() { boolean b1 = userService.hasMatchUser("admin","123465"); boolean b2 = userService.hasMatchUser("admin","111111"); assertTrue(b1); assertTrue(!b2); } @Test public void testFindUserByUserName()throws Exception{ for(int i =0; i< 100;i++) { User user = userService.findUserByUserName("admin"); assertEquals(user.getUserName(), "admin"); } } }
2-7 前端展示层页面
login.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <html> <head> <title>小翔论坛登录</title> </head> <body> <c:if test="${!empty error}"> <font color="red"><c:out value="${error}" /></font> </c:if> <form action="<c:url value="loginCheck.html"/>" method="post"> 用户名: <input type="text" name="userName"> <br> 密 码: <input type="password" name="password"> <br> <input type="submit" value="登录" /> <input type="reset" value="重置" /> </form> </body> </html>
main.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>小翔论坛</title> </head> <body> ${user.userName},欢迎您进入小翔论坛,您当前积分为${user.credits}; </body> </html>
2-8 配置Spring MVC框架
需要对web.xml文件进行配置,使Web容器启动时可以自动启动Spring容器
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <!--①从类路径下加载Spring配置文件,classpath关键字特指类路径下加载--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:smart-context.xml </param-value> </context-param> <!--②负责启动Spring容器的监听器,它将引用①处的上下文参数获得Spring配置文件的地址--> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Spring MVC的主控Servlet,实现地址映射--> <servlet> <servlet-name>smart</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <!-- Spring MVC处理的URL--> <servlet-mapping> <servlet-name>smart</servlet-name> <url-pattern>*.html</url-pattern> </servlet-mapping> </web-app>
2-9 添加控制层
package com.javahx.web; import com.javahx.domain.DO.LoginCommand; import com.javahx.domain.User; import com.javahx.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import java.util.Date; @Controller public class LoginController { @Autowired private UserService userService; @RequestMapping(value="/index.html") public String loginPage(){ return "login"; } @RequestMapping(value = "/loginCheck.html") public ModelAndView loginCheck(HttpServletRequest request, LoginCommand loginCommand) { boolean isValidUser = userService.hasMatchUser(loginCommand.getUserName(), loginCommand.getPassword()); if (!isValidUser) { return new ModelAndView("login","error","用户名或密码错误"); } else { User user = userService.findUserByUserName(loginCommand.getUserName()); user.setLastIp(request.getLocalAddr()); user.setLastVisit(new Date()); userService.loginSuccess(user); request.getSession().setAttribute("user",user); return new ModelAndView("main"); } } }
创建LoginCommand.class业务实体类 public class LoginCommand { private String userName; private String password;
省略get和set。。。。。。。
}
2-10 Spring MVC配置文件
创建smart-servlet.xml,扫描控制层的文件目录处理注解和Bean的注入,并且添加页面路径的解析
<?xml version="1.0" encoding="UTF-8" ?> <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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 扫描web包,应用Spring的注解 --> <context:component-scan base-package="com.javahx.web"/> <!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 --> <!--通过prefix指定在视图名前所添加的前缀,通过suffix指定在视图名后所添加的后缀--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:viewClass="org.springframework.web.servlet.view.JstlView" p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/> </beans>
2-11 使用jetty运行web运用,当然也可以在IDE工具中配置Web应用服务器,比如tomcat
服务启动成功后,
浏览测试地址为:http://localhost:8000/bbs/index.html
三、重点回顾
3-1、proxy-target-class="true/false"事务配置说明
proxy-target-class="true" ,强制JAVA走cglib来生成动态代理,反之则是用JDK AOP来生成代理(基于接口做代理)
JDK动态代理和CGLIB字节码生成的区别?
JDK动态代理只能对实现了接口的类生成代理,而不能针对类
CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中 的方法。因为是继承,所以该类或方法最好不要声明成final。
注意事项:即使你未声明 proxy-target-class="true" ,但运行类没有继承接口,spring也会自动使用CGLIB代理。
高版本spring自动根据运行类选择 JDK 或 CGLIB 代理。
JDK动态代理是模拟接口实现的方式,cglib是模拟子类继承的方式,一般采用前者,因为前者效率高。后者不建议使用,除非非用不可 。
代码示例如下:
/**
* AOP测试类
*/
public class AopTest {
@Test
public void testProxyTargetClass(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-test-aop.xml");
//proxy-target-class="true/false",都支持
// UserService userService = (UserService)context.getBean("userServiceImpl");
//proxy-target-class="true",为false时会报转换错误
UserServiceImpl userService = (UserServiceImpl)context.getBean("userServiceImpl");
userService.addUser();
}
}
四、报错回顾
3-1 java.lang.NoSuchMethodError: org.springframework.core.io.ResourceEditor错误
如果使用数据库的事务管理,缺少引用如下,则会报错
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
3-2 idea解决maven的依赖包版本不一致的问题
一般是包版本不一致导致的,可以通过打开Maven Projects窗口查看版本是否一致