MyBatis
day01
Spring MVC 的异常处理
1 普通的异常处理方式有什么不足
普通的异常处理方式可以是try … catch … finally,或者throw + throws。
在一个项目中,可能有些异常出现的频率高较,而频繁的在代码中使用以上处理异常的语法可能比较繁琐。
2 使用SimpleMappingExceptionResolver
通过使用SimpleMappingExceptionResolver
,可以配置异常与某些页面的映射关系,配置完成后,无论是处理哪个请求时出现了指定的异常,都会直接到指定的页面!
SimpleMappingExceptionResolver
是Spring MVC中已经存在的类,我们需要做的就是在Spring MVC的配置文件中通过<bean>
节点配置它,并且在其中配置异常与页面的映射关系即可,例如:
<!-- 配置处理异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.NullPointerException">error1</prop>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error2</prop>
</props>
</property>
</bean>
这种做法比较简单,但是处理得相对比较粗糙,提示的信息不够精准!
3 使用@ExceptionHandler
在Controller
中,可以自定义方法,使用@ExceptionHandler
进行注解,一旦处理请求过程中出现异常,Spring MVC就会自动调用该方法。
自定义的方法和平时使用的处理请求的方法一样,方法名称可以自定义,参数可以根据需要自行添加,通常,至少添加Exception
作为参数,以判断异常的类型。
@ExceptionHandler
public String handleException(HttpServletRequest request,
Exception ex) {
request.setAttribute("errorMessage", ex.getMessage());
if (ex instanceof NullPointerException) {
return "error3";
} else if (ex instanceof ArrayIndexOutOfBoundsException) {
return "error4";
} else {
return "error5";
}
}
这种做法也很简单,由于是在程序中进行处理,所以,自由度更高!
这种做法与使用SimpleMappingExceptionResolver
是冲突的,在使用这种做法之前,先确保没有使用SimpleMappingExceptionResolver
,不要同时使用这2种方式处理相同的异常。
在使用这种做法时,如果需要多个Controller
都应用这种处理异常的方式,可以把以上代码写一个父类中,然后每个Controller
都继承自这个类!
public abstract class ExceptionController {
@ExceptionHandler
public String handleException() { .... }
}
public class MainController extends ExceptionController {
// 这个类用于处理各种请求
}
MyBatis
1 基本概念
MyBatis是在项目中用于解决数据存储的框架,即访问数据库的框架。
使用了MyBatis后,在访问数据库时,只需要定义接口,并配置接口中的抽象方法对应的SQL语句即可,而不需要开发接口的实现类!
2 基本使用
2.1 添加依赖
创建新项目:10-MyBatis-01-Sample
,执行常规操作,并添加以下依赖:
Group ID Artifact Version
org.springframework spring-webmvc 3.2.8.RELEASE
commons-dbcp commons-dbcp 1.4
mysql mysql-connector-java 5.1.37
junit junit 4.12
org.springframework spring-jdbc 3.2.8.RELEASE
mybatis mybatis 3.2.8
org.mybatis mybatis-spring 1.3.1
操作完成后,检查在src\main\resources
下是否存在数据库连接池的.properties
配置文件、文件内是否正确配置,检查Spring的配置文件中是否加载了这个配置、是否配置了BasicDataSource
。
2.2 设定目标
向tedu_ums
数据库的t_user
数据表中添加1条用户数据,数据中包括username、password、phone、email。
2.3 【开发】创建持久层Java代码
创建cn.tedu.ssm.bean.User
实体类,声明id和以上4个属性。
创建cn.tedu.ssm.mapper.UserMapper
接口,在接口中声明:
void createUser(User user);
2.4 【开发】创建持久层的映射
先从FTP服务器下载mapper.zip并解压缩。
在src\main\resources
中新建mappers
文件夹,然后粘贴解压缩得到的文件,并重命名为UserMapper.xml
。
在UserMapper.xml
的根节点<mapper>
中,添加namespace
属性,值为匹配的Java接口文件的全名:
<mapper namespace="cn.tedu.ssm.mapper.UserMapper">
</mapper>
然后,在<mapper>
中添加子级节点,以对应Java接口中的抽象方法,如果该方法执行的是增加数据操作,则添加<insert>
节点,同理,如果执行的是删除操作,则添加<delete>
节点,以此类推。此次显然使用<insert>
节点,必须配置的属性是id
,值为抽象方法的名称,即:
<mapper namespace="cn.tedu.ssm.mapper.UserMapper">
<!-- 通过子节点配置与抽象方法的映射关系 -->
<insert id="createUser">
</insert>
</mapper>
由于对应的方法是有参数的,则在<insert>
节点上配置parameterType
属性以确定参数的类型,取值为该类型的全名。
然后,在<insert>
节点内部,编写此次操作需要执行的SQL语句,在SQL语句中,变量使用#{}
表达式,如果SQL语句中变量是方法的参数的某个属性,可以直接写属性名,例如:
<mapper namespace="cn.tedu.ssm.mapper.UserMapper">
<!-- 通过子节点配置与抽象方法的映射关系 -->
<insert id="createUser"
parameterType="cn.tedu.ssm.bean.User">
INSERT INTO t_user (
username, password,
phone, email
) VALUES (
#{username}, #{password},
#{phone}, #{email}
)
</insert>
</mapper>
2.5 【开发】配置MyBatis
在Spring的配置文件中,配置:
<!-- 配置MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 用于配置持久层接口在哪里 -->
<property name="basePackage" value="cn.tedu.ssm.mapper" />
</bean>
<!-- 配置SqlSessionFactoryBean -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 用于配置数据库连接池 -->
<property name="dataSource" ref="dataSource" />
<!-- 用于配置持久层映射文件在哪里 -->
<property name="mapperLocations"
value="classpath:mappers/UserMapper.xml" />
</bean>
2.6 【开发】获取新增加的数据的ID
如果需要获取新记录的ID,则在<insert>
节点中需要添加2个属性:useGeneratedKeys="true"
和keyProperty="id"
,例如:
<insert id="createUser"
parameterType="cn.tedu.ssm.bean.User"
useGeneratedKeys="true"
keyProperty="id">
当添加这些配置以后,在MyBatis执行增加操作后,会把新记录的ID封装到用于增加操作的对象中,即:如果调用createUser()
方法时,使用的User userA
对象作为参数,则执行完后,userA
中就有了新记录的ID:
userMapper.createUser(user);
Integer id = user.getId();
2.7 小结:如何使用Spring整合后的MyBatis
-
添加依赖,依赖的jar包的版本不限,但可以统一版本号的请尽量统一,特别是以
spring-
作为前缀的! -
配置
spring-mvc.xml
,在这个文件中,至少要配置:组件扫描、ViewResolver、读取db.properties、配置dbcp(BasicDataSource)、配置MapperScannerConfigurer、配置SqlSessionFactoryBean,如果这些配置是从其它的项目中复制过来的,请检查值是否需要修改! -
编写接口文件,并定义抽象方法,如果需要相关实体类,则创建所需的实体类
-
配置对应的映射:Mapper.xml文件可以从FTP下载,或从此前的项目中复制,通常推荐将这个文件存放在
src\main\resources\mappers
下(请与Spring配置中保持一致),然后编辑文件的内容
3 练习:查询特定的某条数据
3.1 设定目标
在以上基础之上,添加根据用户名查询数据的功能。
3.2 【开发】持久层–接口
在UserMapper.java
接口中添加新的抽象方法:
User findUserByUsername(String username);
3.3 【开发】持久层–映射
在UserMapper.xml
映射文件中添加<select>
子节点:
<select id="findUserByUsername"
resultType="cn.tedu.ssm.bean.User">
SELECT
id, username, password, phone, email
FROM
t_user
WHERE
username=#{username}
</select>
在配置这些节点时,如果接口中声明的方法只有1个参数,并且是基本数据类型或String类型,可以不需要通过parameterType
指定参数的数据类型。
由于此次操作是需要返回值的,则需要在<select>
节点添加resultType
属性指定返回值的数据类型。
3.4 【测试】
在测试类中进行测试。
4 练习:查询多条数据
4.1 设定目标
查询所有用户数据,不指定查询条件
4.2 【开发】持久层–接口
在UserMapper.java
接口中添加新的抽象方法:
List<User> findAllUser();
4.3 【开发】持久层–映射
在UserMapper.xml
映射文件中添加<select>
子节点:
<select id="findAllUser"
resultType="cn.tedu.ssm.bean.User">
SELECT
id, username, password, phone, email
FROM
t_user
</select>
4.4 【测试】
在测试类中进行测试。
5 练习:删除数据
5.1 设定目标
根据ID删除数据
5.2 【开发】持久层–接口
在UserMapper.java
接口中添加新的抽象方法:
Integer delete(Integer id);
5.3 【开发】持久层–映射
在UserMapper.xml
映射文件中添加<delete>
子节点:
<delete id="delete">
DELETE FROM
t_user
WHERE
id=#{id}
</delete>
5.4 【测试】
在测试类中进行测试。
6 练习:修改数据
6.1 设定目标
根据id修改密码、手机号码、电子邮件,不允许修改用户名。
6.2 【开发】持久层–接口
在UserMapper.java
接口中添加新的抽象方法:
Integer updateUserInfo(User user);
6.3 【开发】持久层–映射
在UserMapper.xml
映射文件中添加<update>
子节点:
<update id="updateUserInfo"
parameterType="cn.tedu.ssm.bean.User">
UPDATE
t_user
SET
password=#{password},phone=#{phone},email=#{email}
WHERE
id=#{id}
</update>
6.4 【测试】
在测试类中进行测试。
day02
关于修改数据时的问题
1 问题
通过MyBatis实现修改数据,配置的映射是:
<update id="updateUserInfo"
parameterType="cn.tedu.ssm.bean.User">
UPDATE
t_user
SET
password = #{password},
phone = #{phone},
email = #{email}
WHERE
id=#{id}
</update>
所以,如果调用该功能时,假设参数中并没有封装phone的数据,则默认为null,最终数据表中该记录的phone字段将被赋null值。
2 解决目标
如果调用该功能时,没有提供新的phone的数据,则不修改原有的phone数据,对于password、email的数据也是同样处理!
/**
* 修改用户数据,不支持修改用户名
* @param user 必须封装被修改的用户的ID,
* 不需要封装用户名,
* 对于phone、email、password的字段,
* 如果需要修改,则封装新的值,
* 如果不需要修改,则无须封装值
* @return 如果修改完成,则返回1,
* 如果需要修改的数据不存在,则返回0。
*/
Integer updateUserInfo(User user);
3 实现思路
先通过ID查询被修改的用户数据,在执行修改之前,对新数据进行判断,如果某些字段为null值,则使用从数据表中查询到的数据封装进去即可。
即:假设修改id=1的数据,先查询这条数据,以手机号为例,假设从数据表中查询到的手机号是13900139001
,如果修改时,使用的User对象中没有封装手机号码,则使用则查询到的手机号码封装到User对象中,最终在执行修改时,User对象中会包含与数据表中一致的手机号码,则可以实现设计目标。
4 实现步骤
4.1 在持久层实现根据ID查询数据的功能
在UserMapper.java
接口中添加新的抽象方法:
User findUserById(Integer id);
在UserMapper.xml
中配置映射。
4.2 创建业务层接口
创建cn.tedu.ssm.service.IUserService
接口,添加所有持久层已经实现的方法:
4.3 创建业务层实现类
创建cn.tedu.ssm.service.UserServiceImpl
类,实现以上业务层接口,在类中声明private UserMapper userMapper;
并使用@Resource
进行注解,还需使用@Serivce("userService")
对类进行注解,实现过程中,直接调用持久层来完成,暂时不编写业务逻辑。
4.4 测试
使用单元测试
MyBatis 动态SQL
1基本概念
在使用MyBatis时,配置映射文件(.xml)时,使用的SQL语句可以添加一些语句,从而使得最终的SQL语句是可变的!
2 基本使用
在编写映射中的SQL语句时,例如可以添加进行判断,使得SQL语句中的其中一部分会根据判断条件来决定是否需要。例如:
<update id="update"
parameterType="cn.tedu.ssm.bean.User">
UPDATE
t_user
SET
password = #{password},
<if test="phone != null">
phone = #{phone},
</if>
email = #{email}
WHERE
id=#{id}
</update>
3 练习if的使用
3.1 设定目标
在项目中,需要实现:“修改密码”、“修改手机号码”、“修改电子邮箱”这3个功能
3.2 分析
在映射文件中,使用<if>
即可判断到底执行哪个字段的更新操作。
3.3 实现功能
-
在
UserMapper.java
接口中声明名称较为泛化的方法:Integer update(User user);
-
配置
UPDATE t_user SET password = #{password} phone = #{phone} email = #{email} WHERE id=#{id}UserMapper.xml
映射: -
单元测试,测试时需要注意:在User对象,只封装password、phone、email中的某1项,而不要同时封装多项!
-
在业务逻辑层
IUserService
接口中声明抽象方法:Integer updatePassword(Integer id, String password);
Integer updatePhone(Integer id, String phone);
Integer updateEmail(Integer id, String email);
-
在业务逻辑层
UserServiceImpl
实现类中实现以上方法 -
单元测试
4 什么时候需要使用动态SQL
如果使用了动态SQL,可以根据参数的值的不同,使得最终执行的SQL语句不同。
使用动态SQL可以使得同一个方法可以实现多种不同的目标,例如以上update()
方法最终可以演变为updatePassword()
、updatePhone()
等方法。
所以,动态SQL是对SQL语句无法编程的一种补充。
但是,动态SQL只是为了便于利用SQL语句,并不是为了解决业务中存在的问题的!
AJAX
1 基本概念
AJAX用于向服务器发出异步请求,并获取结果的技术。
使用AJAX的本质还是在使用JavaScript编程。
可以想像为:AJAX就是一个看不到的浏览器,用于向服务器发出请求,并获取响应结果!
当AJAX请求获取响应结果后,可以结合JavaScript实现对网页的某个部分的显示效果进行更新,俗称“局部刷新”。
使用AJAX相对于传统的请求方式而言,无论是用户体验还是访问效率都会高许多!
使用AJAX必须在原有的页面的基础之上才可以!
2 练习
2.1 设计目标
设计login.html
页面,用于模拟登录,在这个页面中添加1个<form>
,其中包含2个<input type="text" />
分别用于输入用户名和密码,还包含1个<input type="button" />
按钮。
当点击按钮后,将提交POST
类型的请求,提交到http://SERVER:PORT/PROJECT/handle_login.do
中,假设正确的用户名是ajax
,匹配的密码是ajax888
,如果提交的数据是正确的,则响应1
,如果提交的数据无法登录,则响应0
。
最终,如果得到的登录结果是1
,使用alert()
函数提示登录成功!否则,在输入用户名的输入框下显示登录失败
的字样。
2.2 开发
2.2.1 创建项目
创建项目11-AJAX-DAY01-Login
,生成web.xml
,添加Tomcat Runtime,添加依赖:spring-webmvc
,复制spring-mvc.xml
文件并检查文件内容,配置web.xml
!
2.2.2 显示登录页面
设计登录页面的访问路径:http://SERVER:PORT/PROJECT/login.do
开发实际显示的页面:WEB-INF/login.jsp
,页面中至少包括以上设计的标签。
创建cn.tedu.ajax.controller.UserController
类,使用@Controller
注解,并添加方法以处理请求,处理的方式是直接转发:
@RequestMapping("/login.do")
public String showLogin() {
return "login";
}
2.2.3 测试访问页面
在浏览器输入网址,需要能够正确的显示登录页面。
2.2.4 服务器端响应
在服务器端的UserController
中添加处理“登录”请求的方法,在完成测试之前先使用GET
方法的请求:
@RequestMapping(method=RequestMethod.GET,
value="/handle_login.do")
public String handleLogin() {
}
需要接收用户提交的用户名和密码,所以,需要在方法中添加2个参数:
public String handleLogin(String username, String password) {
}
然后,在方法中对用户名和密码进行判断:
if ("ajax".equals(username) && "ajax888".equals(password)) {
// 登录成功,响应"1"
} else {
// 登录失败,响应"0"
}
如果需要响应的是一个字符串,而不是响应某个View组件,在处理请求的方法之前添加@ResponseBody
注解即可!
@RequestMapping(method=RequestMethod.GET,
value="/handle_login.do")
@ResponseBody
public String handleLogin(String username, String password) {
if ("ajax".equals(username) && "ajax888".equals(password)) {
// 登录成功,响应"1"
return "1";
} else {
// 登录失败,响应"0"
return "0";
}
}
并且,在spring-mvc.xml
中,在根节点下添加:<mvc:annotation-driven />
。
2.2.5 测试提交后的登录处理
在浏览器输入网址例如:http://http://localhost:8080/11-AJAX-DAY01-Login/handle_login.do?username=ajax&password=ajax888
测试完成后,可将处理请求的请求类型调整为POST
。
2.2.6 发出AJAX请求
为了便于使用,先创建一个函数,专门用于获取XMLHttpRequest
对象:
<script type="text/javascript" language="javascript">
function getXMLHttpRequest() {
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHttp");
}
return xhr;
}
</script>
然后,在登录界面中为2个输入框都设置id属性:
<input id="username" type="text" name="username" />
<input id="password" type="text" name="password" />
然后,再编写发出请求的函数,以备调用:
function handleLogin() {
var xhr = getXMLHttpRequest();
var u = document.getElementById("username").value;
var p = document.getElementById("password").value;
var url = "handle_login.do?username=" + u + "&password=" + p;
xhr.onreadystatechange = funtion() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert(xhr.responseText);
}
//else {
// alert("出错啦!!!");
//}
};
xhr.open("GET", url, true);
xhr.send();
}
在“登录”中配置onclick="handleLogin()"
。
AJAX的使用方式
核心对象
在AJAX中,使用XMLHttpRequest对象发出请求,并获取响应结果。获取对象的方式与Java中相同:
var xhr = new XMLHttpRequest();
以上语法适用于主流浏览器,包括:Chrome、FireFox、Safari、IE 8+,对于低版本的IE浏览器,需要:
var xhr = new ActiveXObject("Microsoft.XMLHttp");
为了保证兼容低版本的IE,应该:
var xhr;
if (window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
xhr = new ActiveXObject("Microsoft.XMLHttp");
}
核心对象的使用
如果需要发出请求,必须先调用XMLHttpRequest
对象的open(method, url, isAsync)
函数,然后调用send()
函数。
如果需要处理结果,还需要配置XMLHttpRequest
对象的onreadystatechange
属性,它的值是另一个处理函数,即需要另编写一个函数决定如何处理响应结果。
所以,正确的使用方式:先配置onreadystatechange
属性,然后调用open()
函数,最后调用send()
函数!
核心对象的属性
a) onreadystatechange:值是处理响应结果的函数
b) readyState:准备状态,其值是数值,共5个,分别是:0-尚未初始化、1-与服务器已经建立连接、2-已发出请求、3-请求已接收,并正在处理、4-已响应
c) status:响应码,例如200、404
d) responseText:服务器响应的正文
e) responseXML:服务器响应的XML