实战项目完成代码:MybatisTest_GitHub
上一章:(一)初见MyBatis,快速入门以及配置文件映射文件讲解
1.优化代码
我们先对昨天的代码进行一下小小的优化,在测试类中写的测试方法,如果我们每次再写一个测试方法,都需要构建工厂什么的,就会有很多重复性代码
修改测试方法MybatisTest
public class MybatisTest {
private InputStream in;
private SqlSession session;
private UserDao userDao;
//在测试方法执行之前执行
@Before
public void before() throws IOException {
//1.获取工厂图纸(配置信息)
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.招募工人(创建构建者)
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.给工人图纸建造工厂(构建者构建工厂对象)
SqlSessionFactory factory = builder.build(in);
//4.构建产品流水线(打开自动提交事务)
session = factory.openSession(true);
//5.根据产品设计图生产出产品(动态代理)
userDao = session.getMapper(UserDao.class);
}
//在测试方法执行之后执行
@After
public void after() throws IOException {
//7.回收(释放资源)
session.close();
in.close();
}
@Test
public void findAll() throws IOException {
//6.使用产品
List<User> users = userDao.findAll();
for (User user :
users) {
System.out.println(user);
}
}
}
修改SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--加载外部资源-->
<properties resource="druid.properties"/>
<!--取别名-->
<typeAliases>
<!--单个设置-->
<!--<typeAlias alias="user" type="com.lois.domain.User"/>-->
<!--批量设置,扫描整个包下的类,别名为类名(首字母大写或小写都可以)-->
<package name="com.lois.domain"/>
</typeAliases>
<environments default="mysql">
<!--配置mysql环境-->
<environment id="mysql">
<!--配置事务类型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置数据源(连接池)-->
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--指定映射文件-->
<mappers>
<package name="com.lois.dao"/>
</mappers>
</configuration>
再次运行方法,查看是否成功。
2.增删查改
- 修改UserDao接口
public interface UserDao {
/**
* 查询所有用户
* @return
*/
public List<User> findAll();
/**
* 根据id查询用户
* @param id
* @return
*/
public User findById(int id);
/**
* 保存用户
* @param user
*/
public void save(User user);
/**
* 修改用户
* @param user
*/
public void update(User user);
/**
* 根据id删除用户
* @param id
*/
public void deleteById(int id);
}
- 修改Dao映射文件UserDao.xml
<mapper namespace="com.lois.dao.UserDao">
<select id="findAll" resultType="user" >
select * from user;
</select>
<select id="findById" parameterType="int" resultType="user">
select * from user where id = #{uid}
</select>
<insert id="save" parameterType="user">
insert into user(name,age) values(#{name},#{age})
</insert>
<update id="update" parameterType="user">
update user set name = #{name},age = #{age} where id = #{id}
</update>
<delete id="deleteById" parameterType="int" >
delete from user where id = #{uid}
</delete>
</mapper>
- 向测试类MybatisTest中添加一下方法进行测试
@Test
public void findById(){
//6.使用产品
User user = userDao.findById(1);
System.out.println(user);
}
@Test
public void save(){
User user = new User();
user.setName("小章");
user.setAge(33);
userDao.save(user);
//查看数据库表数据变化
}
@Test
public void update(){
//修改id为3的用户年龄
User user = userDao.findById(3);
System.out.println(user.getAge());
user.setAge(100);
userDao.update(user);
//查看数据库表中id为3的用户年龄和控制台中打印的数字
}
@Test
public void deleteById(){
//删除id为3的用户
userDao.deleteById(3);
}
- 对各个测试方法进行测试,并观察数据库表中数据变化
注:事实上除了select不需要事务提交,其他操作都需要事务提交,如果你factory.openSession(true)中没有设置true,也就是说没有打开自动提交事务的话,虽然你也能看到上面的测试方法执行成功,但是却没有实际效果。如果你没有打开自动提交事务,那么就需要你在每个方法后面进行手动提交事务session.commit()
3.模糊查询
下面我们来讲一下模糊查询,顺便看看在Dao映射文件中#{}和${}的区别
- 向UserDao接口中添加两个方法,一个准备使用#{},一个准备${}
/**
* 模糊查询#{}
* @param name
* @return
*/
public List<User> findLikeName1(String name);
/**
* 模糊查询${}
* @param name
* @return
*/
public List<User> findLikeName2(String name);
- 向UserDao.xml映射文件中添加如下映射
<select id="findLikeName1" parameterType="string" resultType="user">
select * from user where name like #{name}
</select>
<select id="findLikeName2" parameterType="string" resultType="user">
select * from user where name like '%${value}%'
</select>
- 向测试类添加两个测试方法
@Test
public void findLikeName1(){
System.out.println(userDao.findLikeName1("%小%"));
}
@Test
public void findLikeName2(){
System.out.println(userDao.findLikeName2("小"));
}
不知道你发现没有,使用#{}需要在测试方法中拼接%,而使用${}不需要,这是因为#{}不能在 ’ ’ 中使用。
- 测试结果比较
findLikeName1
findLikeName2
结论:我们可以通过输出的sql语句知道,使用#{}是预加载的方法,而${}仅仅只是字符串拼接,所以使用#{}更好,能够防止sql注入。
4.动态SQL语句
在上面我们写的都是一些很简单的sql语句,但是在实际中,我们还会语句很复杂的sql语句,例如动态变化的SQL语句,那么下面我们讲解mybatis如何实现动态sql语句。
4.1 <if>标签
- test属性(必填):判断条件,其中不能使用&&、||,用 and 、or 替代
- 向UserDao接口中添加方法
/**
* 动态sql——if
* @param user
* @return
*/
public List<User> findByUser1(User user);
- 想UserDao.xml映射文件中添加映射
<select id="findByUser1" parameterType="user" resultType="user">
select * from user where 1=1
<if test="name != null and name != '' ">
and name = #{name}
</if>
<if test="age != null and age != 0 ">
and age = #{age}
</if>
</select>
注:where 1=1是为了能够拼接字符串,为了使下面的if都不成立时,sql语句还是正确
- 向数据库表中添加一条数据,修改save测试方法,并运行
@Test
public void save(){
User user = new User();
user.setName("小明");
user.setAge(32);
userDao.save(user);
//查看数据库表数据变化
}
- 向测试类中添加测试方法,运行测试方法
@Test
public void findByUser(){
User user = new User();
user.setName("小明");
//user.setAge(22);
System.out.println(userDao.findByUser1(user));
}
- 放开注释,再次运行
- 对比结果
发现拼接的字符串不同
4.2 <where>标签
<where>标签可以将需不需要加where交给交给mybatis判断,这样就不用写where 1=1 了。
- 修改刚刚在映射文件中的映射
<select id="findByUser1" parameterType="user" resultType="user">
select * from user
<where>
<if test="name != null and name != '' ">
and name = #{name}
</if>
<if test="age != null and age != 0 ">
and age = #{age}
</if>
</where>
</select>
- 修改测试类中测试方法,运行测试方法
@Test
public void findByUser(){
User user = new User();
//user.setName("小明");
//user.setAge(22);
System.out.println(userDao.findByUser1(user));
}
- 依次放开注释,再次运行测试方法
4.3 <foreach>标签
当我们在进行范围查询时,就要将一个集合中的值,作为参数动态添加进来。这样我们将如何进行参数的传递?这时候就要用到<foreach>标签
标签属性介绍:
- collection:所要遍历的集合
- open:拼接字符串的开头
- close:拼接字符串的结尾
- item:集合中取出的单个数据别名
- separator:每个item之间使用什么符合隔开
- <foreach>标签的内容,即为在open和close之间的遍历数据
- 创建一个QueryVo类用于存储List集合参数
public class QueryVo implements Serializable {
private List<Integer> ages;
public List<Integer> getAges() {
return ages;
}
public void setAges(List<Integer> ages) {
this.ages = ages;
}
}
- 向UserDao接口中添加方法
public List<User> findByUserInAge(QueryVo queryVo);
- 向UserDao.xml映射添加映射
<select id="findByUserInAge" resultType="user" parameterType="QueryVo">
select * from user
<where>
<if test="ages != null and ages.size() > 0">
<foreach collection="ages" open="age in ( " close=")" item="age" separator=",">
#{age}
</foreach>
</if>
</where>
</select>
- 向测试类中添加测试方法,运行测试方法
@Test
public void findByUserInAge(){
List<Integer> list = new ArrayList<Integer>();
list.add(22);
list.add(32);
QueryVo queryVo = new QueryVo();
queryVo.setAges(list);
System.out.println(userDao.findByUserInAge(queryVo));
}
- 分析运行结果
可以看到遍历了两个数据,添加在sql语句后面
5 多表查询
5.1 准备工作
- 创建子工程day2
- 将day1中资源都复制过来
- 将UserDao.xml、UserDao接口类和QueryVo实体类删除
- 删除数据库表user中的第三条记录(使user表中只有两条数据,id为1和2)
- 执行sql脚本,创建表account
use mybatistest;
drop table if exists `account`;
create table `account`(
`id` int(10) primary key auto_increment,
`uid` int(10),
`money` double
);
insert into account(uid,money) values (1,2000),(2,3000),(1,3200);
drop table if exists `role`;
create table `role`(
`id` int(10) primary key auto_increment,
`name` varchar(30),
`desc` varchar(60)
);
insert into `role`(`name`,`desc`) values ('董事长','公司的最高领导者'),('总经理','公司管理者'),('财务部长','管理公司财务');
drop table if exists `user_role`;
create table `user_role`(
`uid` int(10),
`rid` int(10)
);
insert into `user_role` values (1,1),(1,2),(2,2),(2,3);
5.2 一对一(多对一)
5.2.1 方式一
- 创建com.lois.domain.Account实体类,生成get、set、tostring方法
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
}
- 编写sql语句
select account.*,user.name,user.age from user,account where user.id = account.uid;
- 根据上图,我们需要编写com.lois.domain.AccountUser实体类,注:该类继承了Account类
public class AccountUser extends Account implements Serializable {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return super.toString()+"AccountUser{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 编写com.lois.dao.AccountDao接口类
public interface AccountDao {
/**
* 查询所有账户,同时获取账户所有人及年龄
* @return
*/
public List<AccountUser> findAll();
}
- 编写com/lois/dao/AccountDao.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">
<mapper namespace="com.lois.dao.AccountDao">
<select id="findAll" resultType="accountuser">
select account.*,user.name,user.age from user,account where user.id = account.uid;
</select>
</mapper>
- 修改测试类com.lois.test.MybatisTest
public class MybatisTest {
private InputStream in;
private SqlSession session;
private AccountDao accountDao;
//在测试方法执行之前执行
@Before
public void before() throws IOException {
//1.获取工厂图纸(配置信息)
in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.招募工人(创建构建者)
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3.给工人图纸建造工厂(构建者构建工厂对象)
SqlSessionFactory factory = builder.build(in);
//4.构建产品流水线(打开自动提交事务)
session = factory.openSession(true);
//5.根据产品设计图生产出产品(动态代理)
accountDao = session.getMapper(AccountDao.class);
}
//在测试方法执行之后执行
@After
public void after() throws IOException {
//7.回收(释放资源)
session.close();
in.close();
}
@Test
public void findAllAccount(){
System.out.println(accountDao.findAll());
}
}
- 运行结果(3条记录)
[Account{id=1, uid=1, money=2000.0}AccountUser{name='小明', age=22},
Account{id=2, uid=2, money=3000.0}AccountUser{name='小红', age=25},
Account{id=3, uid=1, money=3200.0}AccountUser{name='小明', age=22}]
5.2.2 方式二
- 修改com.lois.domain.Account实体类,生成get、set、tostring方法
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
private User user;
}
- 修改AccountDao接口findAll方法的返回值List<Account>
public List<Account> findAll();
- 修改AccountDao.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">
<mapper namespace="com.lois.dao.AccountDao">
<resultMap id="accountMap" type="account">
<id column="id" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!--映射单个对象-->
<association property="user" javaType="user">
<result column="name" property="name"/>
<result column="age" property="age"/>
</association>
</resultMap>
<select id="findAll" resultMap="accountMap">
select account.*,user.name,user.age from user,account where user.id = account.uid;
</select>
</mapper>
- 运行测试
[Account{id=1, uid=1, money=2000.0, user=User{id=null, name='小明', age=22, accounts=null}},
Account{id=2, uid=2, money=3000.0, user=User{id=null, name='小红', age=25, accounts=null}},
Account{id=3, uid=1, money=3200.0, user=User{id=null, name='小明', age=22, accounts=null}}]
5.3 一对多
- 编写sql语句
select user.*,account.* from user left join account on user.id = account.uid;
可以看到表中有两个id,这是因为在user和account中都有id,所以我们要对sql语句进行修改(取别名)
select user.*,account.id as aid,account.uid,account.money from user left join account on user.id = account.uid;
- 修改User实体类,向实体类中添加List<Account> accounts ,并重新生成set和get、tostring方法
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
private List<Account> accounts;
}
- 编写用户持久层com.lois.dao.UserDao接口
public interface UserDao {
/**
* 查询所有用户,并且查询出用户下所有账户信息
* @return
*/
public List<User> findAll();
}
- 编写com/lois/dao/UserDao.xml映射类,需要使用resultMap,因为我们查出来的字段跟实体类不匹配
<?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">
<mapper namespace="com.lois.dao.UserDao">
<resultMap id="userMap" type="user">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<!--collection 是用于建立一对多中集合属性的对应关系
ofType 用于指定集合元素的数据类型-->
<collection property="accounts" ofType="account">
<id column="aid" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select user.*,account.id as aid,account.uid,account.money from user left join account on user.id = account.uid;
</select>
</mapper>
- 向测试类com.lois.test.MybatisTest添加方法,userDao在测试类中自行获取
@Test
public void findAllUser(){
System.out.println(userDao.findAll());
}
- 运行测试
[User{id=1, name='小明', age=22, accounts=[Account{id=1, uid=1, money=2000.0}, Account{id=3, uid=1, money=3200.0}]},
User{id=2, name='小红', age=25, accounts=[Account{id=2, uid=2, money=3000.0}]}]
可以看见查出了两个用户,用户1名下有2个账户
5.4 对多对
前面我们学习了一对多,多对多其实就可以看成双向的一对多关系。一个用户可以有多个角色,一个角色可以有多个用户
- 编写sql语句
select role.*,user.name as uname,user.age from role left join user_role on (role.id = user_role.rid) left join user on (user_role.uid = user.id);
- 编写实体类com.lois.domain.Role ,生成set,get,tostring方法
public class Role implements Serializable {
private Integer id;
private String name;
private String desc;
private List<User> users;
}
- 编写com.lois.dao.RoleDao接口类
public interface RoleDao {
/**
* 查询所有角色,以及拥有角色的用户
* @return
*/
public List<Role> findAll();
}
- 编写com/lois/dao/RoleDao.xml映射文件,因为我们在sql语句中使用了别名,所以需要使用resultMap
<?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">
<mapper namespace="com.lois.dao.RoleDao">
<resultMap id="roleMap" type="role">
<id column="id" property="id"/>
<result column="name" property="name"/>
<result column="desc" property="desc"/>
<collection property="users" ofType="user">
<result column="uname" property="name"/>
<result column="age" property="age"/>
</collection>
</resultMap>
<select id="findAll" resultMap="roleMap">
select role.*,user.name as uname,user.age from role left join user_role on (role.id = user_role.rid) left join user on (user_role.uid = user.id);
</select>
</mapper>
- 向测试类com.lois.test.MybatisTest添加方法,roleDao在测试类中自行获取
@Test
public void findAllRole(){
System.out.println(roleDao.findAll());
}
- 运行测试
[Role{id=1, name='董事长', desc='公司的最高领导者', users=[User{id=null, name='小明', age=22, accounts=null}]},
Role{id=2, name='总经理', desc='公司管理者', users=[User{id=null, name='小明', age=22, accounts=null}, User{id=null, name='小红', age=25, accounts=null}]},
Role{id=3, name='财务部长', desc='管理公司财务', users=[User{id=null, name='小红', age=25, accounts=null}]}]
可以看到查询出来了三个角色,在角色总经理上有两个用户(当然这可能不符合常理,但是我们也是测试)
6.作业
刚刚我们说了多对多是两个一对多,我们上面已经完成了一个一对多,那么另一个“一用户对多角色”就需要你自己补充了!
上一章:(一)初见MyBatis,快速入门以及配置文件映射文件讲解(构建思路讲解,图文教学,整套实战)
下一章:(三)Mybatis延迟加载(懒加载)、一(二)级缓存、注解开发(简单易懂,图文教学,整套实战)
相关内容:
(一)初见MyBatis,快速入门以及配置文件映射文件讲解(构建思路讲解,图文教学,整套实战)