项目中使用 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个参数时,该属性的取值是listarray,当该节点对应的方法的参数超过1个时,该属性的取值是参数的名称(是@Param注解中确定的名称);item属性是遍历过程中,取出的数据的名称,是自定义的,在<foreach>节点内部也会使用该名称表示变量;seperator表示分隔符,例如以上删除时,SQL中各个id值应该使用逗号分隔,形成例如1,3,5,7这样的格式,则该属性的值为逗号;还有openclose属性,用于配置整个遍历出来的结果的前缀和后缀,例如在编写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中,以用于判断用户已经登录及显示相关数据!

在设计业务层的方法时,返回值应该使用“操作正确时返回的结果”,而不参考可能出现的业务错误来设计返回值。关于失败的处理,统一使用抛出异常来解决!

为了保证抛出多种异常能形成程序中的分支,应该为不同错误创建不同的异常类,也就是需要自定义异常,例如:UserNotFoundExceptionPasswordNotMatchException,这些异常都应该存放于业务包的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个可变参数,且可变参数必须是该方法的最后一个参数!

posted @ 2019-01-19 10:36  一只特立独行的程序猿  阅读(538)  评论(0编辑  收藏  举报