项目中使用 MyBatis(二)
1. 项目中使用 MyBatis(二)
1.1. 获取新插入的数据的id
如果在插入数据时,就需要实时获取新数据的id,首先,在配置XML时,<insert>
节点需要添加2个属性的配置:
<insert id="xx" parameterType="xx.xx.xx.xx.User"
useGeneratedKeys="true"
keyProperty="id">
</insert>
以上配置中,useGeneratedKeys
表示需要获取自动生成的主键(通常都是自动递增的id),keyProperty
表示当获取了主键的值(id的值),该值将被封装到哪个属性中,即插入数据时的参数User
类型中的id
属性!
所以,具体的效果是,当尝试调用Integer reg(User user)
方法执行插入时,指定了以上配置后,MyBatis会将新插入的数据的id
封装到user
参数对象中,所以:
userMapper.reg(user);
System.out.println(user.getId());
1.2. MyBatis中的动态SQL
1.2.1. 概念
MyBatis支持动态SQL,具体的表现是:在配置SQL语句时,可以使用一些简单的逻辑代码,例如if、foreach……使得最终编译出来的SQL语句并不是固定的,而是可能随着参数发生变化的!
1.2.2. 使用foreach一次删除多条数据
一次删除多条数据的SQL语句大致是:
DELETE FROM 表名 WHERE id IN (?,?,?)
而实际应用时,id的列表是由用户进行选取得到的,所以,对于开发者而言,根本就不知道id的列表到底是多少!
针对这样的需求,应该使用动态SQL中的foreach,遍历id列表,得到SQL语句 中例如(?,?,?)
这个部分即可。
假设在t_user
表中实现这样的操作,首先,还是在接口中声明抽象方法:
Integer delete(List<Integer> ids);
然后,在配置的SQL映射中:
<delete id="delete">
DELETE FROM
t_user
WHERE
id IN (
<foreach collection="list"
item="id"
separator=",">
#{id}
</foreach>
)
</delete>
在<foreach>
节点中,collection
表示需要遍历的目标,可以是List
集合,也可以是数据,当该节点对应的方法只有1个参数时,该属性的取值是list
或array
,当该节点对应的方法的参数超过1个时,该属性的取值是参数的名称(是@Param
注解中确定的名称);item
属性是遍历过程中,取出的数据的名称,是自定义的,在<foreach>
节点内部也会使用该名称表示变量;seperator
表示分隔符,例如以上删除时,SQL中各个id值应该使用逗号分隔,形成例如1,3,5,7
这样的格式,则该属性的值为逗号;还有open
和close
属性,用于配置整个遍历出来的结果的前缀和后缀,例如在编写SQL语句时没有指定IN
关键字后面的括号时,可以添加open="(" close=")"
这2项配置。
1.2.3. 使用if选择性的更新数据
在有些更新数据的场景里,可能存在:一次性可以更新同一个用户的多项属性数据,但是,如果没有提交其中的某个数据,则该数据不发生变化!例如:在修改个人资料页面中,还可以有“新密码”输入框,如果不想修改密码,则不填写即可。
针对这样的应用需求,可以添加抽象方法:
Integer changeInfo(User user);
然后,配置SQL映射:
UPDATE
t_user
SET
<if test="password != null">
password=#{password},
</if>
phone=#{phone},
email=#{email},
birthday=#{birthday}
WHERE
id=#{id}
2. 业务
2.1. 定位
在MVC的程序设计中,把整个项目的核心划分出了M、V、C这3大部分。
其中,V表示View,即视图,通常指的是HTML或JSP文件,用于显示界面,为用户提供操作入口,用户可以在界面进行点击、输入、选择等操作;
而C表示Controller,即控制器,在原生的JavaEE技术中,指的是Servlet,在SSM框架中,指的是Controller类,主要作用是接收请求,并给予响应,但是,对数据本身并不作实质的处理;
还有M表示Model,即数据模型,具体的指对数据的处理操作,为了更加明确的划分数据的逻辑与执行的操作,所以,在实际编程时,Model表现为Service和Dao/Mapper的组合,其中,Service用于处理业务,而Dao/Mapper处理数据的增删改查;
因此,定位为Service的类,通常称之为业务逻辑类,主要用于设计业务的流程与逻辑,即:什么时候允许做什么、先做什么再做什么……
有了业务逻辑后,更加易于保证数据的安全。
有了业务逻辑后,持久层(Dao/Mapper)也就不再关心业务,只是单纯的实现增删改查即可,而不用考虑能不能增加或修改或删除等。
2.2. 用户注册的业务
2.2.1. 分析业务逻辑
用户注册中就可以有:用户名必须是唯一的,如果尝试注册的用户名已经被占用,则不允许注册!
2.2.2. 实现
首先,持久层(DAO/Mapper)必须具备:根据用户名查询用户信息,以判断某个用户名是否被占用;注册,将用户数据添加到数据表中。
然后,创建业务接口com.company.mybatis.service.IUserService
,并添加抽象方法:
public interface IUserService {
User reg(User user);
}
接下来,创建以上接口的实现类com.company.mybatis.service.impl.UserServiceImpl
,后续对应的查询、注册功能都需要通过持久层(UserMapper
)来完成,所以,在这个类中,需要UserMapper的对象,可以声明为成员变量,然后通过Spring为其自动装配来注入值,当然,要使用自动装配机制,首先得保证整个类(UserServiceImpl
)是能够被Spring管理的,所以,当前类还需要添加@Service
注解,并且,在Spring的配置文件中开启组件扫描,要能够扫到当前类所在的包!
然后再实现以上抽象方法:
@Service("userService")
public class UserServiceImpl implements IUserService {
@Autowired
private UserMapper userMapper;
public User reg(User user) {
// 根据user.getUsername()查询用户信息
String username = user.getUsername();
User result
= userMapper.getUserByUsername(username);
// 判断查询结果是否为null
if (result == null) {
// 是:没有被占用,则允许注册
userMapper.reg(user);
// 返回
return user;
} else {
// 否:已经被占用,则不允许注册
throw new RuntimeException(
"您尝试注册的用户名(" + username + ")已经被占用!");
}
}
}
最后,测试:
public class TestUserService {
@Test
public void reg() {
// 加载Spring的配置文件,获取Spring容器
AbstractApplicationContext ac
= new ClassPathXmlApplicationContext(
"spring-service.xml", "spring-dao.xml");
// 获取所需的对象:IUserService
IUserService service
= ac.getBean("userService", IUserService.class);
// 测试执行
try {
User user = new User("苍教师", "123456");
User result = service.reg(user);
System.out.println("注册成功:" + result);
} catch (RuntimeException e) {
System.out.println("注册失败:" + e.getMessage());
}
// 释放资源
ac.close();
}
}
2.3. 用户登录的业务
2.3.1. 分析
用户需要提交用户名、密码才可以登录,在验证时,应该根据用户名查询用户信息,如果存在,则验证用户输入的密码,与刚才查询结果中的密码是否匹配,如果还能够匹配,则登录成功,如果用户名匹配的数据不存在,或密码不匹配,则登录失败。
2.3.2. 实现登录验证
在IUserService
接口中添加新的抽象方法,表示用户登录:
User login(String username, String password);
首先,方法名可以是简单易懂的,使用login
即可,方法参数取决于用户提交的数据,或必要的数据,则可以是String username, String password
,返回值只考虑登录成功后所需要得到的结果,则设计为User
类型,后续,当控制器(Controller
)调用这个方法,并且成功登录后,可以通过该返回值获取到当前用户的id
、用户名甚至头像等等数据,存放到Session中,以用于判断用户已经登录及显示相关数据!
在设计业务层的方法时,返回值应该使用“操作正确时返回的结果”,而不参考可能出现的业务错误来设计返回值。关于失败的处理,统一使用抛出异常来解决!
为了保证抛出多种异常能形成程序中的分支,应该为不同错误创建不同的异常类,也就是需要自定义异常,例如:UserNotFoundException
、PasswordNotMatchException
,这些异常都应该存放于业务包的ex
子包中,同时,为了便于统一处理异常(至于是仔细处理还是统一处理,都是控制器决定,在写业务层,应该为各种做法都提供基础)还自定义一个ServiceException
,作为当前项目中所有的业务层可能抛出的异常的基类,并且是继承自RuntimeException
的。
然后,在UserServiceImpl
实现类中实现以上方法:
public User login(String username, String password) {
// 根据用户名查询用户信息
// 判断与用户名匹配的用户信息是否存在
// 是:存在,判断参数密码和查询到的用户信息中的密码是否匹配
// -- 是:匹配,登录成功,将查询到的用户信息作为返回值
// -- 否:不匹配,抛出异常:密码错误
// 否:不存在,抛出异常:用户名不存在
}
2.4. 暂时小结
-
业务逻辑类,主要用于设计业务的流程与逻辑,即:什么时候允许做什么、先做什么再做什么……
-
在设计业务层的方法时,返回值应该使用“操作正确时返回的结果”,而不参考可能出现的业务错误来设计返回值。关于失败的处理,统一使用抛出异常来解决!
-
应该创建
ServiceException
类表示当前项目的业务异常的基类,当前项目中所有的业务异常都应该是这个类的子孙类! -
在业务方法的实现中,凡是认定为“错误”或“失败”,均抛出对应的异常对象,并在抛出的对象描述错误信息,如果没有合适的异常类,则创建新的异常类即可!
其它
1. 可变参数
可变参数,表示当调用某个方法时,使用的参数的数量是不确定的,是可能发生变化的,例如:
public class TestSample {
public static void main(String[] args) {
sum();
sum(1);
sum(1, 2);
sum(1, 2, 3);
sum(1, 2, 3, 4);
sum(1, 2, 3, 4, 5);
sum(1, 2, 3, 4, 5, 6);
sum(1, 2, 3, 4, 5, 6, 7);
}
public static void sum(int... numbers) {
int result = 0;
for (int i = 0; i < numbers.length; i++) {
result += numbers[i];
}
System.out.println(result);
}
}
在声明方法时,可变参数使用数据类型... 参数名
来表示,例如:
public static void sum(int... numbers)
在方法体中,处理该参数时,把参数视为数组即可。
在调用方法时,可变参数的数量最少是0个,即可以不提供任何参数。
注意:每个方法中最多只允许存在1个可变参数,且可变参数必须是该方法的最后一个参数!