Feb 1997

Overtime and overdue


  • Home

  • Tags

  • Categories

  • Archives

  • Search

Leetcode:105 & 106

Posted on 2020-05-16

前序遍历:根-左-右
中序遍历:左-根-右
后序遍历:右-左-根
前序遍历和后续遍历可以确定根节点的位置(第一个或最后一个),中序遍历可以确定左子树上有哪些节点,右子树上有哪些节点。这两道题的大致思路就是通过前序或者后序遍历,确定根节点的位置,再通过中序遍历确定左右两边子树的节点,依次迭代。

105

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
return helper(0,0,inorder.length-1,preorder,inorder);
}
public TreeNode helper(int preStart,int inStart, int inEnd, int[] preorder, int[] inorder) {
if(inStart>inEnd || preStart >= preorder.length) {return null;}
// root
TreeNode root = new TreeNode(preorder[preStart]);
int index = 0;
for(int i = index; i < inorder.length; i++) {
if(inorder[i]==preorder[preStart]) {
index = i;
break;
}
}
root.left = helper(preStart+1,inStart,index-1,preorder,inorder);
root.right = helper(preStart+index-inStart+1,index+1,inEnd,preorder,inorder);
return root;
}
}

难点在于计算右子树的preStart。因为题中给出的例子左子树只有一个节点,容易忽略考虑左子树的长度。

106

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
return helper(postorder.length-1,0,inorder.length-1,inorder,postorder);
}
public static TreeNode helper(int postEnd, int inStart, int inEnd, int[] inorder, int[] postorder) {
if(postEnd>=postorder.length || inStart>inEnd) {return null;}
TreeNode root = new TreeNode(postorder[postEnd]);
int index = 0;
for(int i = 0; i < inorder.length; i++) {
if(inorder[i]==postorder[postEnd]) {
index = i;
break;
}
}
root.left = helper(postEnd-inEnd+index-1,inStart,index-1,inorder,postorder);
root.right = helper(postEnd-1,index+1,inEnd,inorder,postorder);
return root;
}
}

难点在于计算左子树的preStart。因为题中给出的例子左子树只有一个节点,容易忽略考虑左子树的长度。

LeetCode:199

Posted on 2020-05-15

递归法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
helper(root,res,0);
return res;
}

public void helper(TreeNode root, List<Integer> res, int level) {
if(root==null) {return;}
if(res.size()==level) {res.add(root.val);}
helper(root.right,res,level+1);
helper(root.left,res,level+1);
}
}

考虑到会出现树不平衡的情况:
1
/ \\
2 3
\\
5
所以既要考虑到左边的子树,也要考虑到右边的子树。此例中如果每次递归都只考虑最右边的子树的节点,会发现3没有子节点,这时候就没办法回到2节点了。
这种解法的技巧在于用level来确保每一层只会选取一个节点。先递归右子树再递归左子树则解决了不平衡的问题:如果右子树没了,再考虑同一层的左子树。

SSM读取excel文件更新数据库

Posted on 2020-05-13

需求

模仿新增库存的功能,前端上传excel文件(.xlsx)传入后端实现批量增加。

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--        adding support for multipart file upload functionality to servlets and web applications-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<!-- Java API To Access Microsoft Format Files-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<!-- Java API To Access Microsoft Format Files-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>

这里我之前犯了一个错误,我在导入1.3.3版本commons-fileupload的同时,还导入了2.4版本commons-io的依赖,最后启动项目的时候会有冲突,原因是commons-fileupload中已经包含有了commons-io且版本不同,故产生冲突。

前端

1
2
3
4
<form action="${pageContext.request.contextPath}/book/batchAdd" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="file">
<input type="submit" value="提交">
</form>

enctype就是encodetype就是编码类型的意思。

multipart/form-data是指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思。

需要注意的是:默认情况下,enctype的值是application/x-www-form-urlencoded,不能用于文件上传,只有使用了multipart/form-data,才能完整的传递文件数据。

application/x-www-form-urlencoded不是不能上传文件,是只能上传文本格式的文件,multipart/form-data是将文件以二进制的形式上传,这样可以实现多种类型的文件上传。

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RequestMapping("/batchAdd")
public String batchAdd(MultipartFile file) throws IOException, InvalidFormatException {

String originalFileName = file.getOriginalFilename(); // 获取原文件的名字,这里是books.xlsx
String myPath = "D:\\fileupload\\"+originalFileName;
file.transferTo(new File(myPath)); // 把获取到的文件file转化到目标文件,这里是在目录里创建了一个同样的文件

XSSFWorkbook wb = new XSSFWorkbook(new File(myPath)); // 当文件后缀为xlsx时用XSSFWorkbook

XSSFSheet xssfSheet = wb.getSheetAt(0); // 因为这文件里只有一张sheet,所以直接getSheetAt(0)写死

if (xssfSheet != null) {
for (int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {
XSSFRow xssfRow = xssfSheet.getRow(rowNum); // 获取行
Books books = new Books(); // 对每一行创建一个对象
books.setBookName(xssfRow.getCell(0).getStringCellValue());
books.setBookCounts((int)xssfRow.getCell(1).getNumericCellValue());
books.setDetail(xssfRow.getCell(2).getStringCellValue());
bookService.addBook(books); //执行addBook把每一个对象加进去
}
}
return "redirect:/book/allBook";
}

MyBatis+Mysql实现有则更新,无则插入

Posted on 2020-05-13

MySQL实现

1
2
3
insert into books (`bookName`,`bookCounts`,`detail`) 
values ('MySQL',5,'快跑辣')
on duplicate key update `bookCounts`=`bookCounts`+values(`bookCounts`),`detail` = values(`detail`);

错误

1
2
3
4
5
<insert id="addBook" parameterType="Books">
insert into books (bookName,bookCounts,detail)
values (#{bookName},#{bookCounts},#{detail})
on duplicate key update bookCounts=bookCounts+values(#{bookCounts}), detail=values(#{detail}) ;
</insert>

日志:

1
2
==>  Preparing: insert into books (bookName,bookCounts,detail) values (?,?,?) on duplicate key update bookCounts=bookCounts+values(?), detail=values(?) ; 
==> Parameters: MySQL(String), 2(Integer), 2(String), 2(Integer), 2(String)

正确

1
2
3
4
5
6
<insert id="addBook" parameterType="Books">
insert into books (bookName,bookCounts,detail)
values (#{bookName},#{bookCounts},#{detail})
// 下面不用#{}取数值
on duplicate key update bookCounts=bookCounts+values(bookCounts), detail=values(detail) ;
</insert>

日志:

1
2
==>  Preparing: insert into books (bookName,bookCounts,detail) values (?,?,?) on duplicate key update bookCounts=bookCounts+values(bookCounts), detail=values(detail) ; 
==> Parameters: MySQL(String), 2(Integer), 2(String)

Java:Lambda Comparator

Posted on 2020-04-20 Edited on 2020-05-26

原文地址:https://mkyong.com/java8/java-8-lambda-comparator-example/

在这个例子中,我将会展示如何使用Java 8 Lambda表达式去写一个Comparator来对List进行排序。

  1. 传统Comparator例子:

    1
    2
    3
    4
    5
    6
    Comparator<Developer> byName = new Comparator<Developer>() {
    @Override
    public int compare(Developer o1, Developer o2) {
    return o1.getName().compareTo(o2.getName());
    }
    };
  2. 等价的Lambda表达式

    1
    2
    Comparator<Developer> byName = 
    (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());

不用Lambda排序

举个通过age属性对Developer对象进行排序的例子。通常,你使用Collections.sort然后传入匿名Comparator类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class TestSorting {
public static void main(String[] args) {
List<Developer> listDevs = getDevelopers();

System.out.println("Befor sort");
for(Developer developer:listDevs) {
System.out.println(developer);
}

//sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1,Developer o2) {
return o1,getAge()-o2.getAge();
}
});

System.out.println("After Sort");
for(Developer developer:ListDevs) {
System.out.println(developer);
}
}

private static List<Developer> getDevelopers() {
List<Developer> result = new ArrayList<Developer>();

result.add(new Developer("mkyong", new BigDecimal("70000"),33));
result.add(new Developer("alvin", new BigDecimal("80000"),20));
result.add(new Developer("jason", new BigDecimal("100000"),10));
result.add(new Developer("iris", new BigDecimal("170000"),55));

return result;
}
}

输出:

1
2
3
4
5
6
7
8
9
10
11
Before Sort
Developer [name=mkyong, salary=7000, age=33]
Developer [name=alvin, salary=8000, age=20]
Developer [name=jason, salary=10000, age=10]
Developer [name=iris, salary=17000, age=55]

After Sort
Developer [name=jason, salary=10000, age=10]
Developer [name=alvin, salary=8000, age=20]
Developer [name=mkyong, salary=7000, age=33]
Developer [name=iris, salary=17000, age=55]

如果排序要求变了,你只需要传入另一个匿名Comparator类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getAge()-o2.getAge();
}
});
// sort by name
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getNmae());
}
});
//sort by salary
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getSalary().compareTo(o2.getSlary());
}
});

这方法有效,但是你不觉得这样有点奇怪吗,因为你只想改变一行代码却创建了一个新的类。

用Lambda排序

在Java 8中,List接口直接支持sort方法,不需要再用Collections.sort

1
2
3
4
5
6
7
// Java 8 之后的List.sort()
listDevs.sort(new Comparator<Deceloper>() {
@Override
public int compare(Developer o1, Developer o2) {
return o2.getAge()-o1.getAge();
}
});

Lambda表达式例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class TestSorting {
public static void main(String[] args) {
List<Developer> listDevs = getDevelopers();

System.out.println("Before Sort");
for(Developer developer : listDevs) {
System.out.println(developer);
}
System.out.println("After Sort");
// lambda
listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());
listDevs.forEach((developer)->System.out.println(developer));
}
private static List<Developer> getDevelopers() {
List<Developer> result = new ArrayList<Developer>();

result.add(new Developer("mkyong", new BigDecimal("70000"),33));
result.add(new Developer("alvin", new BigDecimal("80000"),20));
result.add(new Developer("jason", new BigDecimal("100000"),10));
result.add(new Developer("iris", new BigDecimal("170000"),55));

return result;
}

}

输出和前面的例子一样。

更多lambda的例子

Sort by age

1
2
3
4
5
6
7
8
9
10
11
// sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getAge()-o2.getAge();
}
});
//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());
//同样有效的lambda,参数类型不是必须的
listDevs.sort((o1, o2)->o1.getAge()-o2.getAge());

Sort by Name

1
2
3
4
5
6
7
8
9
10
11
// sort by name
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getName());
}
});
//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getName().compareTo(o2.getName()));
//同样有效的lambda,参数类型不是必须的
listDevs.sort((o1, o2)->o1.getName().compareTo(o2.getName()));

Sort by salary

1
2
3
4
5
6
7
8
9
10
11
//sort by salary
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getSalary().compareTo(o2.getSalary());
}
});
//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getSalary().compareTo(o2.getSalary()));
//同样有效的lambda,参数类型不是必须的
listDevs.sort((o1, o2)->o1.getSalary().compareTo(o2.getSalary()));

反向排序

1
2
Comparator<Developer> salaryComparator = (o1, o2)->o1.getSalary().compareTo(o2.getSalary());
listDevs.sort(salaryComparator.reversed());

个人理解就是把前面的正向排序的lambda表达式拆分城两部分,一部分是用lambda表达式把Comparator实现,另一部分则是把Comparator的对象作为参数传入sort方法,并且可以使用.reversed()


Q. What is Comparable and Comparator Interface in java?

Comparable and Comparator both are interfaces and can be used to sort collection elements.

Comparable Comparator
1) Comparable provides a single sorting sequence. In other words, we can sort the collection on the basis of a single element such as id, name, and price. The Comparator provides multiple sorting sequences. In other words, we can sort the collection on the basis of multiple elements such as id, name, and price etc.
2) Comparable affects the original class, i.e., the actual class is modified. Comparator doesn’t affect the original class, i.e., the actual class is not modified.
3) Comparable provides compareTo() method to sort elements. Comparator provides compare() method to sort elements.
4) Comparable is present in java.lang package. A Comparator is present in the java.util package.

5) We can sort the list elements of Comparable type by Collections.sort(List) method.|We can sort the list elements of Comparator type by Collections.sort(List, Comparator) method.|

MyBatis:二级缓存

Posted on 2020-04-11
  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存。
  • 工作机制:
    • 一个会话查询一个数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的绘画查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查处的数据会放在自己对应的缓存(map)中

步骤:

  1. config.xml中开启全局缓存,虽然默认就是开启的

    1
    2
    <!--        显式地开启全局缓存-->
    <setting name="cacheEnabled" value="true"/>
  2. 在SQL映射文件中添加标签并配置参数,也可以不配置,只写一个<cache/>:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <cache
    <!-- 清除策略FIFO -->
    eviction="FIFO"
    <!-- 每隔60秒刷新 -->
    flushInterval="60000"
    <!-- 最多可以存储结果对象或列表的 512 个引用 -->
    size="512"
    <!-- 返回的对象被认为是只读的 -->
    readOnly="true"/>

MyBatis:缓存

Posted on 2020-04-11

简介

什么是缓存?

  • 存在内存中的数据
  • 将用户经常查询的数据存放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
    为什么使用缓存?
  • 减少和数据库的交互次数,减少系统开销,提高系统效率
    什么样的数据能使用缓存?
  • 经常查询并且不经常改变的数据

    MyBatis缓存

  • MyBatis包含一个非常浅的嘎的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存
    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。

一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间(从SqlSession创建到关闭)查询到的数据会放在本地缓存中
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

测试

  1. 启动日志
  2. 根据Id搜索用户
  3. 搜索同一个Id两次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @Test
    public void addInitBlog(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    User user1 = mapper.queryById(1);
    System.out.println(user1);
    System.out.println("==========");
    User user2 = mapper.queryById(1);
    System.out.println(user2);
    System.out.println(user1==user2);

    sqlSession.close();
    }

日志输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Opening JDBC Connection
Created connection 2088371948.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7c7a06ec]
==> Preparing: select * from user where id=?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, xiaoke, 666
<== Total: 1
User{id=1, name='xiaoke', pwd='666'}
==========
User{id=1, name='xiaoke', pwd='666'}
true
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@7c7a06ec]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@7c7a06ec]
Returned connection 2088371948 to pool.

结果分析

  1. 只执行了一次Sql语句
  2. 两个对象是相等的

    缓存失效

  3. 查询不同的东西
  4. 增删改操作,可能会改变数据库的内容,肯定会刷新缓存
  5. 查询不同的Mapper.xml
  6. 手动清理缓存(sqlSession.clearCache();)

一级缓存的实现就是一个Map,可以通过打断点来证明。

缓存顺序

  1. 先看二级缓存中有没有
  2. 再看一级缓存中有没有
  3. 查询数据库

MyBatis:动态SQL

Posted on 2020-04-11

介绍

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
简而言之,动态SQL简化了SQL语句拼接

环境

1
2
3
4
5
6
7
8
create table blog
(
id varchar(50) not null comment '博客id',
title varchar(100) not null comment '博客标题',
author varchar(30) not null comment '博客作者',
create_time datetime not null comment '创建时间',
views int(30) not null comment '浏览量'
);

已经提前加入几条数据。

if

接口

1
2
3
4
public interface BlogMapper {
// 根据条件查询博客
List<Blog> queryBlogIF(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
9
10
<select id="queryBlogIF" parameterType="map" resultType="Blog">
select * from blog
where 1=1
<if test="title!=null">
and title =#{title}
</if>
<if test="author!=null">
and author =#{author}
</if>
</select>

官方文档上写where state='ACTIVATE',但是在我的环境中会报错,用where 1=1替代是一样的效果,表示恒成立,即使if后面的语句不执行,也能查询出东西。
完整标签为<if test="判断条件"></if>

choose(when,otherwise)

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

接口

1
2
3
4
public interface BlogMapper {
// 根据条件查询博客
List<Blog> queryBlogChoose(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<select id="queryBlogChoose" parameterType="map" resultType="Blog">
select * from blog
<where>
<choose>
<when test="title!=null">
title=#{title}
</when>

<when test="author!=null">
and author=#{author}
</when>

<otherwise>
and views=#{views}
</otherwise>
</choose>
</where>
</select>

注意:此处使用\标签替代了上一个例子中的where 1=1语句,相比后者,前者更优雅地实现了智能拼接。在此例中,如果”title!=null”不成立且”author!=null”成立,在最后拼接时\标签可以识别出where and author=#{author}这样的错误语句并去掉and。

set

接口

1
2
3
public interface BlogMapper {
int updateBlog(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title!=null">
title = #{title},
</if>
<if test="author!=null">
author =#{author},
</if>
</set>
where id = #{id}
</update>

在使用update语句时容易出现的拼接问题是逗号后面紧跟一个where,例如此例中可能会出现update blog set author=#{author}, where id=#{id}的情况,因此\标签也和\标签一样可以智能地识别多余的逗号并去除。

SQL片段

我们可以用\标签把上述三个例子中都出现的SQL片段抽取出来

1
2
3
4
5
6
7
8
<sql id="if-title-author">
<if test="title!=null">
title = #{title}
</if>
<if test="author!=null">
and author =#{author}
</if>
</sql>

需要使用的时候:

1
<include refid="if-title-author"/>

即可实现代码的复用。
注意事项:

  • 最好基于单表来定义SQL片段
  • 不要存在where标签

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。
假设现在需要查询id为1,2,3的三条数据,SQL语句为select * from blog where 1=1 and (id=1 or id=2 or id=3)

使用foreach标签

接口

1
2
3
public interface BlogMapper {
List<Blog> queryBlogForeach(Map map);
}

BlogMapper.xml

1
2
3
4
5
6
7
8
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<foreach collection="ids" item="id" open="and (" separator="or" close=")">
id=#{id}
</foreach>
</where>
</select>

foreach标签中collection是集合,item是集合中的对象(很类似于java中的for(:)语句),open是开头,seperator是分隔符,close是结尾。以这样的方式来拼接SQL语句。
此例中最终拼接成的语句为:select * from blog where 1=1 and (id=1 or id=2 or id=3)

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public void test(){
SqlSession sqlSession = MybatisUtil.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

Map map = new HashMap();
ArrayList<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);


List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}

MyBatis:collection

Posted on 2020-04-11

和association一样,collection用于处理复杂关系。不同的是association用于映射单个对象,而collection用于映射集合对象。

场景

创建student表:

1
2
3
4
5
6
7
8
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

创建teacher表:

1
2
3
4
5
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

要求

根据teacher的id查出老师手下的所有学生

MySQL

1
2
3
select t.name tname, s.id sid, s.name sname
from teacher t, student s
where t.id=s.tid and t.id=#{tid}

MyBatis实现

实体类(pojo)

Student.java:

1
2
3
4
5
6
public class Student {
private int id;
private String name;
private int tid;
<!-- getter,setter,toString -->
}

Teacher.java:

1
2
3
4
5
6
public class Teacher {
private int id;
private String name;
private List<Student> students;
<!-- getter,setter,toString -->
}

与association的要求不同,此处的要求是根据老师的id属性去查询学生,因此构建实体类时也需要变化。

接口类

用了@Param(“tid”)后,xml中的输入也要注意用tid,xml中会进一步解释。

1
2
3
4
public interface TeacherMapper {
Teacher getTeacher(@Param("tid") int id);
Teacher getTeacher2(@Param("tid") int id);
}

TeacherMapper.xml

子查询

先根据输入的id查询老师,然后从根据tid查询出学生

1
2
3
4
5
6
7
8
9
10
11
12
<select id="getTeacher" resultMap="TeacherStudent" >
<!-- id=#{tid}意为接受参数 -->
select * from teacher where id=#{tid}
</select>

<resultMap id="TeacherStudent" type="Teacher">
<collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherID"/>
</resultMap>

<select id="getStudentByTeacherID" resultType="Student">
select * from student where tid=#{id}
</select>

此方法需要注意的是collection标签中的column对应的是Teacher类中的id属性(注意resultMap的type是Teacher),javaType应该写ArrayList,因为student属性是一个集合,ofType用于获取集合中的泛型。

结果集嵌套查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select id="getTeacher2" resultMap="TeacherStudent2">
select t.name tname, s.id sid, s.name sname
from teacher t, student s
<!-- id=#{tid}意为接受参数 -->
where t.id=s.tid and t.id=#{tid}
</select>

<resultMap id="TeacherStudent2" type="Teacher">
<result property="name" column="tname"/>
<result property="id" column="tid"/>
<!-- 集合中泛型的信息,用ofType获取 -->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>

同association,先查询出结果,然后再用resultMap进行说明。

MyBatis:association

Posted on 2020-04-10 Edited on 2020-04-11

场景

创建student表:

1
2
3
4
5
6
7
8
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

创建teacher表:

1
2
3
4
5
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

要求

查询 学生学号,学生姓名,老师姓名

MySQL

1
2
3
select s.id ,s.name ,t.name
from student s,teacher t
where s.tid=t.id

MyBatis实现

实体类(pojo)

Student.java:

1
2
3
4
5
6
public class Student {
private int id;
private String name;
private Teacher teacher;
<!-- getter,setter,toString -->
}

Q:为什么学生属性中用Teacher teacher而不是int tid?
A:如果写int tid就相当于写“死”了,没有达到学生和老师关联的效果。
Teacher.java:

1
2
3
4
5
public class Teacher {
private int id;
private String name;
<!-- getter,setter,toString -->
}

StudentMapper.xml

按照结果嵌套处理

1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.name tname
from student s, teacher t
where s.tid=t.id;
</select>

<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>

思路就是先写好SQL语句,再用resultMap去解释里面的内容。
Student类中的属性id对应结果中的sid,属性name对应结果中的sname。
属性teacher由于是一个复杂属性,所以用association,这里有一种递进的感觉:第一层说明属性teacher的类型是Teacher,第二层说明teacher对象的name属性对应结果中的tname。
查询中子集有相同字段取出的数据就会有问题,因此要采用别名的形式。

子查询

先查询所有学生的信息,再根据查询出来的学生的tid,寻找对应的老师

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 查询所有学生 -->
<select id="getStudent" resultMap="StudentTeacher">
select * from student;
</select>

<!-- 上下两个select中建立连接 -->
<resultMap id="StudentTeacher" type="Student">
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

<!-- 根据id查询老师 -->
<select id="getTeacher" resultType="Teacher">
select * from teacher where id=#{tid};
</select>

复杂的属性(Teacher)需要单独处理,如果是一个对象就用 Association,此处为Teacher对象。
property=”teacher”:学生类里的属性叫teacher
column=”tid”:数据库里的字段叫tid
javaType=”Teacher”:因为属性是个复杂类型,所以要给它定义类型Teacher
select=”getTeacher”:嵌套查询,getTeacher为下面select的id

注意

ResultMap标签中的COLUMN字段对应的不是数据库表中的字段名,而是select语句返回的字段名,当字段有重复时应对其进行重命名

1…456…12
Feb 1997

Feb 1997

112 posts
4 categories
24 tags
© 2020 Feb 1997