目录
一、多表操作
1.1一对一查询
1.2一对多查询
1.3多对多查询
1.4小结
二、MyBatis注解开发
2.1注解实现增删改查
2.2注解实现复杂映射开发
2.2.1一对一查询的注解开发
2.2.2一对多查询的注解开发
2.2.3多对多查询的注解开发
三、SSM框架整合
3.1原始整合
3.2Spring整合MyBatis
实际开发中,一个项目通常需要很多张表才能完成。
例如:一个商城项目就需要分类表(category)、商品表(products)、订单表(orders)等多张表。且这些表的数据之间存在一定的关系。
1、一对一查询的模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户。
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
从上图我们可以看出orders对应user是一对一的关系,一个订单只能对应有一个用户。
2、一对一查询代码实现
首先我们新建一个订单实体类Order,
class Order {
private int id;//订单号
private Date orderTime;//下单时间
private double total;//订单的总金额
private User user;//表明当前订单属于哪个用户
...//此处省略get、set方法和toString方法
}
然后我们给该实体类创建一个OrderMapper接口,里面写一个方法,
interface OrderMapper {
public List findAll();
}
然后根据接口定义的返回值和方法名,对OrderMapper.xml进行配置,
这里我们对返回值类型重新指定了一个集合类型orderMap,里面手动制定了字段和实体属性的映射关系。(不指定的话则框架不会帮我们给Order实体中的user对象进行赋值)
我们还可以使用association标签给Order实体中的user对象进行关系指定,
接着我们进行测试,
@Test
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);
List orderList = orderMapper.findAll();
for (Order order : orderList) {
System.out.println(order);
}
}
1、一对多查询的模型
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
从上图我们可以看出user对应orders是一对多的关系,一个用户可以有多个订单。
首先我们要在User实体类中定义好对应的属性,包括订单的信息,
class User {
private int id;
private String username;
private String password;
private Date birthday;
private List orderList;//当前用户存在哪些订单
//省略了set、get方法和toString方法
}
接着定义一个UserMapper接口,里面写一个方法
interface UserMapper {
public List findAll();
}
然后根据接口的方法,写UserMapper.xml文件,配置好sql语句,
这里我们使用的是collection标签对User实体中的order对象进行赋值,
然后进行测试,
@Test//一对多查询
public void test2() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List userList = userMapper.findAll();
for (User user : userList) {
System.out.println(user);
}
}
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色
从上图和看出user和role之间为多对多关系。
首先哦我们要在数据库中建立好三个表,以及他们的主键和外键关系。
然后我们新建一个role实体类,里面定义属性以及对应的set、get方法和toString方法。
class Role {
private int id;
private String roleName;
private String roleDesc;
//省略了set、get方法和toString方法
}
我们还要在User的实体类中加入role的信息,
public class User {
private int id;
private String username;
private String password;
private Date birthday;
private List orderList;//当前用户存在哪些订单
private List roleList;//当前用户具备哪些角色
//省略set、get方法和toString方法,这里自己生成一下
}
然后在UserMapper接口中定义一个新的方法,用于查询用户和角色信息,
public List findUserRole();
接着在UserMapper.xml中根据接口的方法进行配置,
然后我们进行测试,
@Test//多对多查询
public void test3() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List userList = userMapper.findUserRole();
for (User user : userList) {
System.out.println(user);
}
}
MyBatis多表配置方式
我们知道在MyBatis指定sql语句时,用到的是xml配置的方法,那么同样我们也可以用注解配置。
这几年来注解开发越来越流行,MyBatis也可以使用注解开发方式,这样我们就可以减少编写Mapper 映射文件了。
下面是MyBatis中常用的一些注解:
我们先围绕一些基本的CRUD来学习,再学习复杂映射多表操作。
xml配置实现我们都已经比较了解了,就是先定义接口,然后写xml配置文件,给接口中的方法映射对应的sql语句,最后执行。
使用注解的方式的话就可以不使用mapper.xml来配置sql语句和对应的方法之间的关系了,我们直接在接口定义的方法上用注解来配置对应的sql语句。
interface UserMapper {
@Insert("insert into user(id,username,password) values (#{id},#{username},#{password})")
public void insertUser(User user);
@Update("update user set username=#{username},password=#{password} where id=#{id}")
public void updateUser(User user);
@Delete("delete from user where id=#{id}")
public void deleteUser(int id);
@Select("select * from user")
public List findAll();
@Select("select * from user where id=#{id}")
public User findById(int id);
}
此外,我们还需要在sqlMapConfig核心配置文件中加载我们定义的映射关系,
然后我们进行测试,这里我们为了方便测试,将用于获取usermapper对象的代码抽取出来,使用AOP思想将这段代码作为前置通知,在每个测试方法执行之前执行,
private UserMapper userMapper;
@Before
public void before() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");//获取配置文件输入流
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//获取session工厂对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);//获取session对象,并设置事务为自动提交
userMapper = sqlSession.getMapper(UserMapper.class);
}
然后我们测试一下插入操作,
@Test//插入操作
public void test1(){
User user=new User();
user.setUsername("annotationTest");
user.setPassword("123");
userMapper.insertUser(user);
}
同样的其他增删改查操作也是可以成功运行的。
@Test//修改操作
public void test2(){
User user=new User();
user.setId(4);
user.setUsername("annotationTest");
user.setPassword("321");
userMapper.updateUser(user);
}
@Test//删除操作
public void test3(){
userMapper.deleteUser(4);
}
@Test//根据id查询用户
public void test4(){
User user = userMapper.findById(2);
System.out.println(user);
}
@Test//查询所有用户
public void test5(){
List userList = userMapper.findAll();
for (User user : userList) {
System.out.println(user);
}
}
实现复杂关系映射之前我们可以在映射文件中通过配置
而使用注解开发后,我们可以使用@Results注解,@Result注解,@One注解,@Many注解组合完成复杂关系的配置。
注解 |
说明 |
@Results |
代替的是标签<resultMap> 该注解中可以使用单个@Result注解,也可以使用@Result集合。 使用格式:@Results({@Result(),@Result()})或@Results(@Result()) |
@Result |
代替了 @Result中属性介绍: column:数据库的列名 property:需要装配的属性名 one:需要使用的@One 注解(@Result(one=@One)())) many:需要使用的@Many 注解(@Result(many=@many)())) |
@One(一对一) |
代替了<assocation> 标签 是多表查询的关键,在注解中用来指定子查询返回单一对象。 @One注解属性介绍: select: 指定用来多表查询的 sqlmapper 使用格式:@Result(column=" ",property="",one=@One(select="")) |
@Many(多对一) |
代替了 使用格式:@Result(property="",column="",many=@Many(select="")) |
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户
首先我们创建两个实体对象User和Order,Order中有一个属性为User对象,代表了当前订单是哪个用户的,该关系为一对一。(因为之前已经生成了User类,所以这里我们只用创建Order实体)
class Order {
private int id;//订单号
private Date orderTime;//下单时间
private double total;//订单的总金额
private User user;//表明当前订单属于哪个用户
//省略set、get方法和toString方法,需要自己生成
}
第二步我们使用注解配置Mapper映射关系,创建一个OrderMapper接口,定义一个查询方法,配置好对应的关系,
interface OrderMapper {
@Select("select *,o.id oid from orders o,user u where o.uid=u.id")
@Results({
@Result(column = "oid",property = "id"), //将数据表中oid属性封装到Order对象的id属性中
@Result(column = "ordertime",property = "orderTime"), //ordertime->orderTime
@Result(column = "total",property = "total"), //total->total
@Result(column = "uid",property = "user.id"), //uid->user.id
@Result(column = "username",property = "user.username"), //username->user.username
@Result(column = "password",property = "user.password") //password->user.password
})
public List findAll();
}
最后我们进行测试,注意这里我们要修改一下之前用@Before注解写的前置通知的方法,获取OrderMapper的对象,
private OrderMapper orderMapper;
@Before
public void before() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");//获取配置文件输入流
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);//获取session工厂对象
SqlSession sqlSession = sqlSessionFactory.openSession(true);//获取session对象,并设置事务为自动提交
orderMapper = sqlSession.getMapper(OrderMapper.class);
}
@Test//一对一查询
public void test1(){
List orderList = orderMapper.findAll();
for (Order order : orderList) {
System.out.println(order);
}
}
我们还有另一种查询语句也可以查到订单及对应的用户信息,利用 @One 标签将Order表中的uid字段提取出来,去查询user表中的数据,并将返回结果封装到Order实体类中的user对象中。
@Select("select * from orders")
@Results({
@Result(column = "id",property = "id"), //将数据表中oid属性封装到Order对象的id属性中
@Result(column = "ordertime",property = "orderTime"), //ordertime->orderTime
@Result(column = "total",property = "total"), //total->total
@Result(
property = "user", //指定要封装的属性名称
column = "uid", //根据哪个字段去查询user表数据,即下面查询语句的参数
javaType = User.class, //指定封装的实体类型
one = @One(select = "Mapper.UserMapper.findById") //select属性代表查询哪个接口的方法来获得数据
)
})
public List findAll2();
用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户
一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单
首先我们要在User实体中加入用户拥有的订单属性,生成对应的set和get方法,
private List orderList;
这里我们首先要在OrderMapper中写一个根据用户id查询订单信息的方法,方便后面给User实体类中封装订单信息,
@Select("select * from order where uid=#{uid}")
public List findByUid(int uid);//根据用户id查询对应的订单信息
然后我们在UserMapper中利用注解配置sql语句和方法的对应关系,
@Select("select * from user")
@Results({
@Result(id=true, column = "id", property = "id"), //id->id,第一个id=true代表当前属性为主键
@Result(column = "username", property = "username"), //username->username
@Result(column = "password", property = "password"), //password->password
@Result(
column = "id",//将id作为参数进行查询,得到的结果封装到orderList属性中
property = "orderList",
javaType = List.class,
many = @Many(select = "Mapper.OrderMapper.findByUid")//指定查询对应的方法
)
})
public List findUserOrder();
接着我们进行测试,
@Test//一对多查询
public void test6(){
List userList = userMapper.findUserOrder();
for (User user : userList) {
System.out.println(user);
}
}
用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用
多对多查询的需求:查询用户同时查询出该用户的所有角色
首先我们要新建role实体类,
class Role {
private int id;
private String roleName;
private String roleDesc;
//省略了set、get方法和toString方法
}
然后我们在User实体类中新加入一个属性,记录该用户拥有的角色,同时生成set、get方法。
private List roleList;//描述用户所对应的角色身份
接着新建一个RoleMapper的接口,定义一个方法,根据userid查询role表中的角色信息,
interface RoleMapper {
@Select("select * from user u,role r,user_role ur where ur.roleid=r.id and ur.userid=#{uid}")
public List findByUId(int uid);
}
然后我们需要在UserMapper接口对sql语句和接口方法进行对应,
@Select("select * from user")
@Results({
@Result(id=true, column = "id", property = "id"), //id->id,第一个id=true代表当前属性为主键
@Result(id=true, column = "username", property = "username"), //username->username
@Result(id=true, column = "password", property = "password"), //password->password
@Result(
column = "id", //将id作为参数进行查询,得到的结果封装到roleList属性中
property = "roleList",
javaType = List.class,
many = @Many(select = "Mapper.RoleMapper.findByUId")//根据用户的uid查询角色信息
)
})
public List findUserRole();
然后进行测试,
@Test//多对多查询
public void test7(){
List userList = userMapper.findUserRole();
for (User user : userList) {
System.out.println(user);
}
}
我们学习完了Spring(数据的注入)、SpringMVC(web层)和MyBatis(DAO层),现在我们看看如何进行对这些框架进行整合开发。
其实Spring和SpringMVC属于同一生态的框架,本身就已经给我们整合好了,我们需要做的是将Spring和MyBatis整合起来。
首先我们创建一个账户表,存储账户的姓名和存款,
然后我们创建一个Maven工程,在pom.xml中导入我们需要的jar包坐标,主要包括:
spring-context、aspectjweaver、spring-jdbc、spring-tx、spring-test、spring-webmvc
servlet-api、javax.servlet.jsp-api
mybatis、mybatis-spring、mysql-connector-java、c3p0、junit、jstl
下一步我们创建实体类Account,定义好属性并生成对应的set和get方法,
class Account {
private Integer id;
private String name;
private Double money;
//...
}
下一步我们开始编写DAO层的Mapper接口,定义帐户的一些操作方法,
interface accountMapper {
public void save(Account account);//保存账户信息
public List findAll();//查询所有账户信息
}
接着我们编写业务层的Service接口及对应的实现,
interface AccountService {
public void save(Account account);
public List findAll();
}
@Service("accountService")
class AccountServiceImpl implements AccountService{
public void save(Account account){
try {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);
accountMapper.save(account);
sqlSession.commit();//提交事务
sqlSession.close();//关闭会话
} catch (IOException e) {
e.printStackTrace();
}
}
public List findAll() {
try {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
AccountMapper accountMapper = sqlSession.getMapper(AccountMapper.class);
List accountList = accountMapper.findAll();
//sqlSession.commit();//查询操作不涉及到数据的更改,所以可以不提交事务
sqlSession.close();//关闭会话
return accountList;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
下一步我们编写web层的Controller类,
@Controller
@RequestMapping("/account")
public class AccountController {
@Autowired//自动注入
private AccountService accountService;
//保存
@RequestMapping("/save")
@ResponseBody//返回的字符串直接显示在页面上,不进行跳转
public String save(Account account){
accountService.save(account);//调用业务层方法
return "保存成功";
}
//查询
@RequestMapping(value = "findAll")
public ModelAndView findAll(){
List accountList = accountService.findAll();//获取查询用户的数据
ModelAndView modelAndView=new ModelAndView();
modelAndView.addObject("accountList",accountList);//添加用户集合数据
modelAndView.setViewName("accountList");//设置要跳转的视图名称
return modelAndView;
}
}
接下来我们开始编写界面,分别有添加用户数据页面和展示用户数据的页面,
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
添加账户信息表单
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Title
展示账户数据列表
账户id
账户名称
账户金额
<%--循环显示用户数据--%>
${account.id}
${account.name}
${account.money}
然后是生成需要的配置文件,包括:
1、Spring的核心配置文件:applicationContext.xml
2、SpringMVC的核心配置文件:spring-mvc.xml
3、MyBatis的核心配置文件:sqlMapConfig.xml及映射文件mapper.xml
insert into account values(#{id},#{name},#{money})
4、其他配置文件:web.xml、jdbc.properties、log4j.properties
contextConfigLocation
classpath:applicationContext.xml
org.springframework.web.context.ContextLoaderListener
DispatcherServlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring-mvc.xml
1
DispatcherServlet
/
CharacterEncodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
CharacterEncodingFilter
/*
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb1
jdbc.username=root
jdbc.password=3837
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=debug, stdout
然后我们给项目配置好tomcat服务器启动,
访问 http://localhost:8080/save.js 进行用户信息的添加,
数据库可以看到数据被添加进去了,然后对数据进行查询 http://localhost:8080/account/findAll,
上述这种整合方式有很明显的弊端,我们在service层调用dao层方法时,为了获取AccountMapper对象,需要读取mybatis的配置文件,然后获得sqlsession对象,从而调用方法获取对象。
这样每次写方法都需要读配置文件等一系列繁琐操作,我们可以将该对象的创建也交给Spring框架产生。后续的事务控制语句也可以交给Spring容器进行声明式事务控制。
第一步那么首先我们要将SqlSessionFactory工厂对象配置到Spring容器中,
因为SqlSessionFactory工厂对象还需要数据源信息,所以我们将数据源的配置也从sqlMapConfig.xml中引入到Spring的核心配置文件applicationContext.xml中
最后mapper包下的映射接口对象我们也放到Spring的核心配置文件扫描,让框架帮我们创建实现类,并放置到Spring容器中。
然后我们的MyBatis的配置文件sqlMapConfig-spring.xml就只剩下了自定别名,
最后我们对service层的代码就可以简化,通过自动注入的方式获取mapper的实现类对象,直接调用方法实现业务,
@Service("accountService")
class AccountServiceImpl implements AccountService{
@Autowired//自动注入的方式获取mapper的实现类对象
private AccountMapper accountMapper;
public void save(Account account){
accountMapper.save(account);
}
public List findAll() {
return accountMapper.findAll();
}
}
第二步我们要将事务的控制交给Spring的声明式事务控制,我们在Spring核心配置文件中进行事务的配置,
然后测试保存操作,可以看到数据库中数据已经写入了。