特殊语法
MYSQL中的COLLATE是什么?
指定排序规则
maven命令
查看依赖jar包
1 | mvn dependency:tree -Dverbose |
打包
1 | mvn clean -U package -pl analysis-tool -am -P dev -Dmaven.source.skip=true -Dmaven.test.skip=true |
要注意的是-P 对大小写敏感
重新生成iml文件
1 | mvn idea:module |
参数
1 | -e 详细错误描述 |
问题汇总
the output path is not specified for module ‘xxx’
Specify the output path in the Project Structure dalog
技巧
SpringBoot启动后直接退出Process finished with exit code 1
一般是有错误,但输出并不是console,可以修改log的输出,也可以用try catch查看异常
spring注解
自动化配置的问题
需要排除的配置
[Spring Boot 排除自动配置的 4 种方法,关键时刻很有用!]
- @SpringBootApplication
- @EnableAutoConfiguration
- @SpringCloudApplication
- spring.autoconfigure.exclude
排除的类总结
- @SpringBootApplication(exclude = DruidDataSourceAutoConfigure.class)
注解用法
@RequestBody和@RequestParam注解使用
@Async
- 可以指定线程池
不生效原因:
mybatis技巧
Mybatis中判断集合的 size 不为空
1 | <if test="null != staffCodeList and staffCodeList.size > 0"> |
判断参数为空
1.判断Double类型:
2.判断Integer类型:
3.判断String类型:
4.判断Date类型:
因为String类型是符合的,但是如果是Integer类型的话,如果变量的值是0,即 faceValue = 0, mybatis在进行 faceValue != ‘’ 的时候会认为 faceValue 的值是空字符串, 即 faceValue == ‘’ 为true;
同理,Double,Date也是如此。所以如果是Integer类型,Double类型,Date类型只需要判断 != null 即可。如果String类型需要判断不等于0,则需要写name != ‘0’.toString(),否则会报错。
mybatis 中 foreach collection的三种用法
mybatis 中 foreach collection的三种用法
Mybatis使用IN语句查询
返回数据
spring数据源配置
使用dynamic-datasource-spring-boot-starter
,并用@DS指定数据源
SpringBoot 配合 druid作为数据库连接池 时需要注意的一点
使用dynamic-datasource-spring-boot-starter做多数据源及源码分析
需要配置master作为默认数据源,否则会报,请检查primary默认数据库设置
。
参考资料
mybatis常用注解
1 介绍
mybatis 最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到了 MyBatis 3提供了新的基于注解的配置。mybatis提供的注解有很多,大致可以分为以下几类:
增删改查:@Insert、@Update、@Delete、@Select、@MapKey、@Options、@SelelctKey、@Param、@InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider
结果集映射:@Results、@Result、@ResultMap、@ResultType、@ConstructorArgs、@Arg、@One、@Many、@TypeDiscriminator、@Case
缓存:@CacheNamespace、@Property、@CacheNamespaceRef、@Flush
绝大部分注解,在xml映射文件中都有元素与之对应,但是不是所有。此外在mybatis-spring中提供了@Mapper注解和@MapperScan注解,用于和spring进行整合。
2 增删改查相关注解
注解 | 使用对象 | 相对应的 XML | 描述 |
---|---|---|---|
@Insert @Update @Delete @Select | 方法 | 这四个注解分别代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串之间先会被填充一个空格再连接成单个完整的字符串。这有效避免了以 Java 代码构建 SQL 语句时的“丢失空格”的问题。然而,你也可以提前手动连接好字符串。属性有:value,填入的值是用来组成单个 SQL 语句的字符串数组。 | |
@Options | 方法 | 映射语句的属性 | 这个注解提供访问大范围的交换和配置选项的入口,它们通常在映射语句上作为属性出现。Options 注解提供了通俗易懂的方式来访问它们,而不是让每条语句注解变复杂。属性有:useCache=true, flushCache=FlushCachePolicy.DEFAULT, resultSetType=FORWARD_ONLY, statementType=PREPARED, fetchSize=-1, timeout=-1, useGeneratedKeys=false, keyProperty=”id”, keyColumn=””, resultSets=””。值得一提的是, Java 注解无法指定 null 值。因此,一旦你使用了 Options 注解,你的语句就会被上述属性的默认值所影响。要注意避免默认值带来的预期以外的行为。 注意: keyColumn 属性只在某些数据库中有效(如 Oracle、PostgreSQL等)。请在插入语句一节查看更多关于 keyColumn 和 keyProperty 两者的有效值详情。 |
@MapKey | 方法 | 这是一个用在返回值为 Map 的方法上的注解。它能够将存放对象的 List 转化为 key 值为对象的某一属性的 Map。属性有: value,填入的是对象的属性名,作为 Map 的 key 值。 | |
@SelectKey | 方法 | 这个注解的功能与 |
|
@Param | 参数 | N/A | 如果你的映射方法的形参有多个,这个注解使用在映射方法的参数上就能为它们取自定义名字。若不给出自定义名字,多参数(不包括 RowBounds 参数)则先以 “param” 作前缀,再加上它们的参数位置作为参数别名。例如 #{param1}, #{param2},这个是默认值。如果注解是 @Param(“person”),那么参数就会被命名为 #{person}。 |
@InsertProvider @UpdateProvider @DeleteProvider @SelectProvider | 方法 | 允许构建动态 SQL。这些备选的 SQL 注解允许你指定类名和返回在运行时执行的 SQL 语句的方法。(自从MyBatis 3.4.6开始,你可以用 CharSequence 代替 String 来返回类型返回值了。)当执行映射语句的时候,MyBatis 会实例化类并执行方法,类和方法就是填入了注解的值。你可以把已经传递给映射方法了的对象作为参数,”Mapper interface type” 和 “Mapper method” 会经过 ProviderContext (仅在MyBatis 3.4.5及以上支持)作为参数值。(MyBatis 3.4及以上的版本,支持多参数传入)属性有: type, method。type 属性需填入类。method 需填入该类定义了的方法名。注意 接下来的小节将会讨论类,能帮助你更轻松地构建动态 SQL。 |
映射器接口示例,假设有以下UserMapper接口:
1 | public interface UserMapper { |
说明:
2.1 @Insert、@Update、@Delete、@Options、@SelectKey注解
mybatis会根据接口方法上的@Insert、@Update、@Delete注解,分别去调用SqlSession的insert、update、delete方法。这个几个方法返回的都是一个int,表示影响的记录行数。
特别的,在UserMapper接口的insert方法上,除了添加了@Insert注解,还添加了@Options注解。在上面的案例中,@Options注解用于获取自动生成主键,并设置到User实体中。此外,@SelectKey注解也可以用于获取自动生成的主键,使用方式如下:
1 | @Insert("INSERT INTO user(id,name) VALUES (#{id},#{name})") |
2.2 @Select注解
在上面的案例中,UserMapper的selectById、selectAll、selectMap、selectByIds、selectPage方法都添加了@Select注解。mybatis会根据方法的返回值类型User、List
2.3 @MapKey注解
特别的,对于返回值是Map的情况,UserMapper的selectMap方法上额外添加了一个@MapKey(“id”)注解,表示将User实例的id属性当做Map的key。
2.4 动态sql与,这是因为SQL中使用了动态sql标签。不管是@Insert、@Update、@Delete、@Select注解,只要SQL里使用了mybatis的动态sql标签(包括:if、choose …when …otherwise、trim 、where、 set、foreach、bind)等,都建议在sql前后分别加上,否则可能会出现一些参数找不到的情况。
2.5 @Param注解
@Param注解用于给方法参数起一个名字。以下是笔者总结的使用原则:
在方法只接受一个参数的情况下,可以不使用@Param。
在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
例如上述案例的selectPage方法接受2个参数,所以其两个参数都使用了@Param注解。@Param注解看起来配置最简单,实际上理解确实最复杂,下面进行详细的介绍。
前面已经提到,当映射器接口定义的方法被调用时,mybatis内部根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法。以下是SqlSession接口的相关方法定义(部分省略):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface SqlSession extends Closeable {
...
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
...
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
...
}
可以看到部分方法接受一个Object类型的parameter参数,另外一些方法则没有此参数。mybatis在执行前,除了会根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法;还会提前对传入映射器接口方法中的参数进行一些处理,然后再调用SqlSession的相应方法。逻辑如下:
1、如果映射器接口方法不接受参数,mybatis在执行时会调用相应无parameter参数的方法重载形式。例如,如UserMapper接口的selectAll方法,其不接受参数,返回值类型为List,因此调用SqlSession不接受paramter参数的selectList方法:
1
<E> List<E> selectList(String statement);
2、如果映射器方法只有一个参数,并且这个参数没有使用@Param注解,则直接用这个参数来调用SqlSession相应接受Object类型parameter方法参数的重载形式。例如,如UserMapper接口的selectByIds方法,其接受1个int[]数组类型参数作为查询条件,返回值类型为List,因此调用SqlSession接受paramter参数的selectList方法:
1
<E> List<E> selectList(String statement, Object parameter);
3、如果映射器方法只接受一个参数,但是使用了@Param注解,也会先封装到Map中;活着映射器方法总是接受多个参数,不管有没有使用@Param注解指定参数,总是会先封装到一个Map中。之后,调用SqlSession的相应方法把这个Map当做parameter参数传入。
例如:UserMapper接口的selectPage方法, 通过@Param(“offset”)和@Param(“limit”)为2个int参数指定了名字。
1
2
@Select("SELECT id,name FROM user LIMIT #{offset},#{limit}")
public List<User> selectPage(@Param("offset") int offset, @Param("limit") int limit);
假设我们传入0和10,那么参数封装后的Map结构如下:
key value
-——————-
offset 0 //1
limit 10 //2
param1 0 //3
param2 10 //4
关于Map中1、2两个key-value,比较好理解,是我们通过@Param注解指定的映射关系。而3、4两个key-value,实际上是参数位置(param1、param2…,下标从1开始)和参数值的映射关系,不管我们有没有使用@Param注解,存在多个参数的情况下,我们总是可以按照位置进行引用。
因此,将UserMapper的selectPage方法定义改成以下形式也是正确的:
1
2
3
4
5
6
7
//使用了@Param注解的情况下,依然根据参数位置进行引用(param1,param2…,下标从1开始)
@Select("SELECT id,name FROM user LIMIT #{param1},#{param2}")
public List<User> selectPage(@Param("offset") int offset, @Param("limit") int limit);
//不使用@Param注解,直接根据参数位置进行引用
@Select("SELECT id,name FROM user LIMIT #{param1},#{param2}")
public List<User> selectPage(int offset, int limit);
显然,根据参数位置进行引用不太直观,因此建议在存在多个参数的情况,总是通过@Param注解显式的指定参数名。
2.6 @InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider注解
这几个注解主要用于动态sql构建。
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public interface UserBuilderMapper {
@SelectProvider(type = UserSqlBuilder.class, method = "buildSelectByIdSql")
public User selectById(@Param("id") int id);
@InsertProvider(type = UserSqlBuilder.class, method = "buildInsertSql")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
public int insert(User user);
@UpdateProvider(type = UserSqlBuilder.class, method = "buildUpdateSql")
public int update(User user);
@DeleteProvider(type = UserSqlBuilder.class, method = "buildDeleteSql")
public int delete(@Param("id") int id);
//建议将sql builder以映射器接口内部类的形式进行定义
public static class UserSqlBuilder {
public static String buildSelectByIdSql(@Param("id") int id) {
return new SQL() {
{
SELECT("id, name");
FROM("user");
WHERE("id=#{id}");
}
}.toString();
}
public static String buildInsertSql(User user) {
return new SQL() {
{
INSERT_INTO("user");
VALUES("name", "#{name}");
}
}.toString();
}
public static String buildUpdateSql(User user) {
return new SQL() {
{
UPDATE("user");
SET("name=#{name}");
WHERE("id=#{id}");
}
}.toString();
}
public static String buildDeleteSql(@Param("id") int id) {
return new SQL() {
{
DELETE_FROM("user");
WHERE("id=#{id}");
}
}.toString();
}
}
}
}
3 结果集映射相关注解
注解
使用对象
相对应的 XML
描述
@Results
方法
结果映射的列表,包含了一个特别结果列如何被映射到属性或字段的详情。属性有:value, id。value 属性是 Result 注解的数组。这个 id 的属性是结果映射的名称。
@Result
方法
在列和属性或字段之间的单独结果映射。属性有:id, column, javaType, jdbcType, typeHandler, one, many。id 属性是一个布尔值,来标识应该被用于比较(和在 XML 映射中的相似)的属性。one 属性是单独的联系,和 相似,而 many 属性是对集合而言的,和相似。它们这样命名是为了避免名称冲突。
@ResultMap
方法
N/A
这个注解给 @Select 或者 @SelectProvider 提供在 XML 映射中的 的id。这使得注解的 select 可以复用那些定义在 XML 中的 ResultMap。如果同一 select 注解中还存在 @Results 或者 @ConstructorArgs,那么这两个注解将被此注解覆盖。
@ResultType
方法
N/A
此注解在使用了结果处理器的情况下使用。在这种情况下,返回类型为 void,所以 Mybatis 必须有一种方式决定对象的类型,用于构造每行数据。如果有 XML 的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的
@ConstructorArgs
方法
收集一组结果传递给一个结果对象的构造方法。属性有:value,它是形式参数数组。
@Arg
N/A
单参数构造方法,是 ConstructorArgs 集合的一部分。属性有:id, column, javaType, jdbcType, typeHandler, select和 resultMap。id 属性是布尔值,来标识用于比较的属性,和 XML 元素相似。
@One
N/A
复杂类型的单独属性值映射。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例。fetchType会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用。
@Many
N/A
映射到复杂类型的集合属性。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例的集合,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用
@TypeDiscriminator
方法
一组实例值被用来决定结果映射的表现。属性有:column, javaType, jdbcType, typeHandler 和 cases。cases 属性是实例数组。
@Case
N/A
单独实例的值和它对应的映射。属性有:value, type, results。results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。
3.1 @ConstructorArgs、@Arg注解
如果实体类没有无参的构造方法,那么我们就必须通过@ConstructorArgs与@Arg注解来提供实体类的构造方法参数信息。例如上述UserMapper的selectById方法,其返回一个User对象。假设User对象只有以下构造方法:
1
2
3
4
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
那么需要在selectById方法上,添加@ConstructorArgs注解,提供构造方法信息,如下:
1
2
3
4
5
@Select("SELECT id,name FROM user where id= #{id}")
@ConstructorArgs({
@Arg(column = "id",javaType = Integer.class,id = true),
@Arg(column = "name",javaType = String.class)})
public User selectById(int id);
注意,通常情况下,我们都建议为实体类提供无参的构造方法的,这是最佳实践的总结,因此@ConstructorArgs注解和@Arg注解基本使用不到。
3.2 @Results、@Result注解
如果实体字段的名称与数据库表字段名称不一致时,我们就需要显式的指定映射关系。这是通过@Results、@Result注解来指定的,例如为UserMapper的selectById指定映射关系:
1
2
3
4
5
@Select("SELECT id,name FROM user where id= #{id}")
@Results(id = "userMap", value = {
@Result(property = "id", column = "id", javaType = Integer.class,jdbcType = JdbcType.INTEGER,id = true),
@Result(property = "name", column = "name",javaType = String.class,jdbcType = JdbcType.VARCHAR)})
public User selectById(int id);
其中:
@Results注解:id属性用于给这个映射关系起一个名字(这里指定的为userMap),其内部还包含了一个@Result[]来表示实体属性和数据库表字段的映射关系
@Result注解:property属性是java实体属性的名称,column表示对应的数据库字段的名称。javaType和JdbcType属性可以不指定。
3.3 @ResultMap注解
上述selectById方法已经通过@Results注解指定了结果映射关系,通过@ConstructorArgs指定了构造方法(必要的情况下才使用),那么在其他的查询方法中,我们不需要重复定义,可以通过@ResultMap来引用@Results的id属性值进行复用。如我们在UserMapper的selectAll方法进行复用:
1
2
3
@Select("SELECT id,name FROM user")
@ResultMap("userMap")
public List<User> selectAll();
3.4 @TypeDiscriminator、@Case注解
鉴别器注解基本没用,有空的时候再补充
3.5 @Many、@One注解
主要用于关联关系映射
假设有作者(author)和文章(article)两张数据库表,一个author可以有多个article,一个article只能属于一个author。相关表结构以及初始数据如下所示:
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
--author表
CREATE TABLE `author` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `author` (`id`, `name`)
VALUES
(1, 'tianshouzhi');
--article表
CREATE TABLE `article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` longtext NOT NULL,
`author_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `article` (`id`, `title`, `content`, `author_id`)
VALUES
(1, 'title1', 'content1', 1),
(2, 'title2', 'content2', 1);
相应的Java实体类如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Article {
private Integer id;
private String title;
private String content;
private Author author;
//...setters getters and toString...
}
public class Author {
private Integer id;
private String name;
private List<Article> articles;
//...setters getters and toString...
}
映射器接口定义分别如下:
ArticleMapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface ArticleMapper {
//1、根据文章id查询文章Article对象,同时通过One注解关联查询出作者Author信息
@Select("SELECT id,title,content,author_id FROM article where id= #{articleId}")
@Results(id = "articleWithAuthor", value = {
@Result(property = "id", column = "id"),
@Result(property = "title", column = "title"),
@Result(property = "content", column = "content"),
//property属性:指定将关联查询的结果封装到Article对象的author属性上
//column属性指定:指定在执行@One注解中定义的select语句时,把article表的author_id字段当做参数传入
//one属性:通过@One注解定义关联查询的语句是AuthorMapper中的findAuthorByAuthorId方法
@Result(property = "author",column = "author_id”,
one = @One(select = "com.tianshouzhi.mapper.AuthorMapper.findAuthorByAuthorId"))})
public Article findArticleWithAuthorByArticleId(@Param("articleId") int articleId);
//2、根据作者(Author)的id查询其所有的文章(Article)
@Select("SELECT id,title,content,author_id FROM article WHERE author_id=#{authorId}")
@Results(id = "articlesWithoutAuthor", value = {
@Result(property = "id", column = "id"),
@Result(property = "title", column = "title"),
@Result(property = "content", column = "content")})
List<Article> findArticlesByAuthorId(@Param("authorId") int authorId);
}
AuthorMapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface AuthorMapper {
//根据作者id查询Author信息
@Select("SELECT id,name FROM author WHERE id=#{authorId}")
Author findAuthorByAuthorId(int authorId);
//根据作者id查询Author信息,通过@Many注解关联查询出所有的文章信息
@Select("SELECT id,name FROM author WHERE id=#{authorId}")
@Results(id = "authorWithArticles", value = {
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name”),
//property属性:指定将关联查询的结果封装到Author对象的articles属性上
//column属性指定:指定在执行@Many注解中定义的select语句时,把author表的id字段当做参数传入
//many属性:指定通过@Many注解定义关联查询的语句是ArticleMapper中的findArticlesByAuthorId方法
@Result(property = "articles",column = "id”,
many = @Many(select = "com.tianshouzhi.mapper.ArticleMapper.findArticlesByAuthorId"))})
Author findAuthorWithArticlesByAuthorId(int authorId);
}
测试如下:
1
2
3
4
5
6
7
8
9
10
@Test
public void testOneAndMany(){
System.out.println("===========通过@One注解查询出Article关联的Auhtor===========");
Article article = articleMapper.findArticleWithAuthorByArticleId(1);
System.out.println(article);
System.out.println("===========通过@Many注解查询出Auhtor关联的Article==========");
Author author = authorMapper.findAuthorWithArticlesByAuthorId(1);
System.out.println(author);
}
控制台输出结果为:
===========通过@One注解查询出Article关联的Auhtor===========
Article{id=1, title=’title1’, content=’content1’, author=Author{id=1, name=’tianshouzhi’, articles=null}}
===========通过@Many注解查询出Auhtor关联的Article==========
Author{id=1, name=’tianshouzhi’, articles=[Article{id=1, title=’title1’, content=’content1’, author=null}, Article{id=2, title=’title2’, content=’content2’, author=null}]}
4 缓存相关注解
注解
使用对象
相对应的 XML
描述
@CacheNamespace
类
为给定的命名空间(比如类)配置缓存。属性有:implemetation, eviction, flushInterval, size, readWrite, blocking 和properties。
@Property
N/A
指定参数值或占位值(placeholder)(能被 mybatis-config.xml内的配置属性覆盖)。属性有:name, value。(仅在MyBatis 3.4.2以上版本生效)
@CacheNamespaceRef
类
参照另外一个命名空间的缓存来使用。属性有:value, name。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定 Java 类型而指定命名空间(命名空间名就是指定的 Java 类型的全限定名),name 属性(这个属性仅在MyBatis 3.4.2以上版本生效)直接指定了命名空间的名字。
@Flush
方法
N/A
如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3及以上)
2.5 @Param注解
@Param注解用于给方法参数起一个名字。以下是笔者总结的使用原则:
在方法只接受一个参数的情况下,可以不使用@Param。
在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
例如上述案例的selectPage方法接受2个参数,所以其两个参数都使用了@Param注解。@Param注解看起来配置最简单,实际上理解确实最复杂,下面进行详细的介绍。
前面已经提到,当映射器接口定义的方法被调用时,mybatis内部根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法。以下是SqlSession接口的相关方法定义(部分省略):
1 | public interface SqlSession extends Closeable { |
可以看到部分方法接受一个Object类型的parameter参数,另外一些方法则没有此参数。mybatis在执行前,除了会根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法;还会提前对传入映射器接口方法中的参数进行一些处理,然后再调用SqlSession的相应方法。逻辑如下:
1、如果映射器接口方法不接受参数,mybatis在执行时会调用相应无parameter参数的方法重载形式。例如,如UserMapper接口的selectAll方法,其不接受参数,返回值类型为List,因此调用SqlSession不接受paramter参数的selectList方法:
1 | <E> List<E> selectList(String statement); |
2、如果映射器方法只有一个参数,并且这个参数没有使用@Param注解,则直接用这个参数来调用SqlSession相应接受Object类型parameter方法参数的重载形式。例如,如UserMapper接口的selectByIds方法,其接受1个int[]数组类型参数作为查询条件,返回值类型为List
1 | <E> List<E> selectList(String statement, Object parameter); |
3、如果映射器方法只接受一个参数,但是使用了@Param注解,也会先封装到Map中;活着映射器方法总是接受多个参数,不管有没有使用@Param注解指定参数,总是会先封装到一个Map中。之后,调用SqlSession的相应方法把这个Map当做parameter参数传入。
例如:UserMapper接口的selectPage方法, 通过@Param(“offset”)和@Param(“limit”)为2个int参数指定了名字。
1 | @Select("SELECT id,name FROM user LIMIT #{offset},#{limit}") |
假设我们传入0和10,那么参数封装后的Map结构如下:
key value
-——————-
offset 0 //1
limit 10 //2
param1 0 //3
param2 10 //4
关于Map中1、2两个key-value,比较好理解,是我们通过@Param注解指定的映射关系。而3、4两个key-value,实际上是参数位置(param1、param2…,下标从1开始)和参数值的映射关系,不管我们有没有使用@Param注解,存在多个参数的情况下,我们总是可以按照位置进行引用。
因此,将UserMapper的selectPage方法定义改成以下形式也是正确的:
1 | //使用了@Param注解的情况下,依然根据参数位置进行引用(param1,param2…,下标从1开始) |
显然,根据参数位置进行引用不太直观,因此建议在存在多个参数的情况,总是通过@Param注解显式的指定参数名。
2.6 @InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider注解
这几个注解主要用于动态sql构建。
1 | public interface UserBuilderMapper { |
3 结果集映射相关注解
注解 | 使用对象 | 相对应的 XML | 描述 |
---|---|---|---|
@Results | 方法 | 结果映射的列表,包含了一个特别结果列如何被映射到属性或字段的详情。属性有:value, id。value 属性是 Result 注解的数组。这个 id 的属性是结果映射的名称。 | |
@Result | 方法 | 在列和属性或字段之间的单独结果映射。属性有:id, column, javaType, jdbcType, typeHandler, one, many。id 属性是一个布尔值,来标识应该被用于比较(和在 XML 映射中的 |
|
@ResultMap | 方法 | N/A | 这个注解给 @Select 或者 @SelectProvider 提供在 XML 映射中的 |
@ResultType | 方法 | N/A | 此注解在使用了结果处理器的情况下使用。在这种情况下,返回类型为 void,所以 Mybatis 必须有一种方式决定对象的类型,用于构造每行数据。如果有 XML 的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的 |
@ConstructorArgs | 方法 | 收集一组结果传递给一个结果对象的构造方法。属性有:value,它是形式参数数组。 | |
@Arg | N/A | 单参数构造方法,是 ConstructorArgs 集合的一部分。属性有:id, column, javaType, jdbcType, typeHandler, select和 resultMap。id 属性是布尔值,来标识用于比较的属性,和 |
|
@One | N/A | 复杂类型的单独属性值映射。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例。fetchType会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用。 | |
@Many | N/A | 映射到复杂类型的集合属性。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例的集合,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用 | |
@TypeDiscriminator | 方法 | 一组实例值被用来决定结果映射的表现。属性有:column, javaType, jdbcType, typeHandler 和 cases。cases 属性是实例数组。 | |
@Case | N/A | 单独实例的值和它对应的映射。属性有:value, type, results。results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。 |
3.1 @ConstructorArgs、@Arg注解
如果实体类没有无参的构造方法,那么我们就必须通过@ConstructorArgs与@Arg注解来提供实体类的构造方法参数信息。例如上述UserMapper的selectById方法,其返回一个User对象。假设User对象只有以下构造方法:
1 | public User(Integer id, String name) { |
那么需要在selectById方法上,添加@ConstructorArgs注解,提供构造方法信息,如下:
1 | @Select("SELECT id,name FROM user where id= #{id}") |
注意,通常情况下,我们都建议为实体类提供无参的构造方法的,这是最佳实践的总结,因此@ConstructorArgs注解和@Arg注解基本使用不到。
3.2 @Results、@Result注解
如果实体字段的名称与数据库表字段名称不一致时,我们就需要显式的指定映射关系。这是通过@Results、@Result注解来指定的,例如为UserMapper的selectById指定映射关系:
1 | @Select("SELECT id,name FROM user where id= #{id}") |
其中:
@Results注解:id属性用于给这个映射关系起一个名字(这里指定的为userMap),其内部还包含了一个@Result[]来表示实体属性和数据库表字段的映射关系
@Result注解:property属性是java实体属性的名称,column表示对应的数据库字段的名称。javaType和JdbcType属性可以不指定。
3.3 @ResultMap注解
上述selectById方法已经通过@Results注解指定了结果映射关系,通过@ConstructorArgs指定了构造方法(必要的情况下才使用),那么在其他的查询方法中,我们不需要重复定义,可以通过@ResultMap来引用@Results的id属性值进行复用。如我们在UserMapper的selectAll方法进行复用:
1 | @Select("SELECT id,name FROM user") |
3.4 @TypeDiscriminator、@Case注解
鉴别器注解基本没用,有空的时候再补充
3.5 @Many、@One注解
主要用于关联关系映射
假设有作者(author)和文章(article)两张数据库表,一个author可以有多个article,一个article只能属于一个author。相关表结构以及初始数据如下所示:
1 | --author表 |
相应的Java实体类如下所示:
1 | public class Article { |
映射器接口定义分别如下:
ArticleMapper
1 | public interface ArticleMapper { |
AuthorMapper
1 | public interface AuthorMapper { |
测试如下:
1 | @Test |
控制台输出结果为:
===========通过@One注解查询出Article关联的Auhtor===========
Article{id=1, title=’title1’, content=’content1’, author=Author{id=1, name=’tianshouzhi’, articles=null}}
===========通过@Many注解查询出Auhtor关联的Article==========
Author{id=1, name=’tianshouzhi’, articles=[Article{id=1, title=’title1’, content=’content1’, author=null}, Article{id=2, title=’title2’, content=’content2’, author=null}]}
4 缓存相关注解
注解 | 使用对象 | 相对应的 XML | 描述 |
---|---|---|---|
@CacheNamespace | 类 | 为给定的命名空间(比如类)配置缓存。属性有:implemetation, eviction, flushInterval, size, readWrite, blocking 和properties。 | |
@Property | N/A | 指定参数值或占位值(placeholder)(能被 mybatis-config.xml内的配置属性覆盖)。属性有:name, value。(仅在MyBatis 3.4.2以上版本生效) | |
@CacheNamespaceRef | 类 | 参照另外一个命名空间的缓存来使用。属性有:value, name。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定 Java 类型而指定命名空间(命名空间名就是指定的 Java 类型的全限定名),name 属性(这个属性仅在MyBatis 3.4.2以上版本生效)直接指定了命名空间的名字。 | |
@Flush | 方法 | N/A | 如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3及以上) |
官方文档目录摘要
[toc]
编译与部署
安装与部署
软硬件需求
- FE(前端)和BE(后端)存储数据的区别,以及所需机器配置
- FE与BE端口、网络需求
- ip绑定
集群部署
手动部署
- FE部署
- BE部署
- FS_Broker部署
扩容缩容
FE扩容和缩容
- 增加FE节点
- 删除FE节点
BE扩容和缩容
- 增加BE节点
- 删除BE节点
Broker扩容缩容
常见问题
开始使用
基础使用指南
1.创建用户
- Root用户登陆与密码修改
- 创建新用户
2.数据表的创建与数据导入
创建数据库
账户授权
建表
- 单分区
- 复合分区
导入数据
- 流式导入
- Broker导入
3.数据的查询
- 简单查询
- Join查询
- 子查询
高级使用指南
1. 表结构变更
2. Rollup
3. 数据表的查询
- 内存限制
- 查询超时
- Broadcast/Shuffle Join
- 查询重试和高可用
最佳实践
1. 建表
数据模型选择
- AGGREGATE KEY
- UNIQUE KEY
- DUPLICATE KEY
大宽表与Star Schema
分区和分桶
- Range分区(partition)
- HASH分桶(bucket)
稀疏索引和Bloom Filter
物化视图(rollup)
- Base Table中数据聚合度不高
- Base Table中的前缀索引无法命中
2. Schema Change
- Sorted Schema Change
- Direct Schema Change: 无需重新排序,但需要对数据做一次转换。例如修改列的类型,在稀疏索引中加一列等
- Linked Schema Change: 无需转换数据,直接完成。例如加列操作
数据划分
1. 基本概念
- Row & Column
- Tablet & Partition
2. 数据划分
列定义
- 列定义建议
分区与分桶
Doris支持两层的数据划分。第一层是Partition,仅支持Range的划分方式。第二层是Bucket(Tablet),仅支持Hash的划分方式。也可以仅使用一层分区
- Partition
- Bucket
- 关于Partition和Bucket的数量和数据量的建议
- 多列分区
PROPERTIES
- replication_num
- storage_medium & storage_cooldown_time
ENGINE
3. 常见问题
建表操作常见问题
- 如果在较长的建表语句中出现语法错误,可能会出现语法错误提示不全的现象。这里罗列可能的语法错误供手动纠错:
- Failed to create partition [xxx] . Timeout
- 建表命令长时间不返回结果。
数据模型、ROLLUP及前缀索引
1. 基本概念
Row
Column
- Key
- Value
2. Aggregate模型
- 示例1: 导入数据聚合
- 示例2: 保留明细数据
- 示例3: 导入数据与已有数据聚合
3. Uniq模型
4. Duplicate模型(冗余模型)
5. ROLLUP
基本概念
Aggregate和Uniq模型中的ROLLUP
- 示例1: 获得每个用户的总消费
- 示例2: 获得不同城市,不同年龄段用户的总消费、最长和最短页面驻留时间
Duplicate模型中的ROLLUP
前缀索引与ROLLUP
- 前缀索引
我们将一行数据的前 36 个字节 作为这行数据的前缀索引。当遇到 VARCHAR 类型时,前缀索引会直接截断。
- ROLLUP调整前缀索引
- 根本作用是提高某些查询的查询效率(无论是通过聚合来减少数据量,还是修改列顺序以匹配前缀索引)。因此 ROLLUP 的含义已经超出了 “上卷” 的范围。这也是为什么我们在源代码中,将其命名为 Materized Index(物化索引)的原因。
- ROLLUP是附属于Base表的,可以看作是Base表的一种辅助数据结构。用户可以在 Base 表的基础上,创建或删除 ROLLUP,但是不能在查询中显式的指定查询某 ROLLUP。是否命中 ROLLUP 完全由 Doris 系统自动决定。
- ROLLUP 的数据是独立物理存储的。因此,创建的 ROLLUP 越多,占用的磁盘空间也就越大。同时对导入速度也会有影响(导入的ETL阶段会自动产生所有 ROLLUP 的数据),但是不会降低查询效率(只会更好)。
- ROLLUP 的数据更新与 Base 表示完全同步的。用户无需关心这个问题。
- ROLLUP 中列的聚合方式,与 Base 表完全相同。在创建 ROLLUP 无需指定,也不能修改。
- 查询能否命中 ROLLUP 的一个必要条件(非充分条件)是,查询所涉及的所有列(包括 select list 和 where 中的查询条件列等)都存在于该 ROLLUP 的列中。否则,查询只能命中 Base 表。
- 某些类型的查询(如 count(*))在任何条件下,都无法命中 ROLLUP。具体参见接下来的 聚合模型的局限性 一节。
- 可以通过 EXPLAIN your_sql; 命令获得查询执行计划,在执行计划中,查看是否命中 ROLLUP。
- 可以通过 DESC tbl_name ALL; 语句显示 Base 表和所有已创建完成的 ROLLUP。
- 查询如何命中ROLLUP
6. 聚合模型的局限性
- Aggregate 模型(包括 Uniq 模型)
在聚合模型中,模型对外展现的,是最终聚合后的数据。也就是说,任何还未聚合的数据(比如说两个不同导入批次的数据),必须通过某种方式,以保证对外展示的一致性。
- Duplicate 模型
Duplicate 模型没有聚合模型的这个局限性。因为该模型不涉及聚合语意,在做 count(*) 查询时,任意选择一列查询,即可得到语意正确的结果。
7. 数据模型的选择建议
因为数据模型在建表时就已经确定,且无法修改。所以,选择一个合适的数据模型非常重要。
- Aggregate 模型可以通过预聚合,极大地降低聚合查询时所需扫描的数据量和查询的计算量,非常适合有固定模式的报表类查询场景。但是该模型对 count(*) 查询很不友好。同时因为固定了 Value 列上的聚合方式,在进行其他类型的聚合查询时,需要考虑语意正确性。
- Uniq 模型针对需要唯一主键约束的场景,可以保证主键唯一性约束。但是无法利用 ROLLUP 等预聚合带来的查询优势(因为本质是 REPLACE,没有 SUM 这种聚合方式)。
- Duplicate 适合任意维度的 Ad-hoc 查询。虽然同样无法利用预聚合的特性,但是不受聚合模型的约束,可以发挥列存模型的优势(只读取相关列,而不需要读取所有 Key 列)。
Rollup与查询
在 Doris 里 Rollup 作为一份聚合物化视图,其在查询中可以起到两个作用:
- 索引
- 聚合数据(仅用于聚合模型,即aggregate key)
但是为了命中 Rollup 需要满足一定的条件,并且可以通过执行计划中 ScanNdoe 节点的 PreAggregation 的值来判断是否可以命中 Rollup,以及 Rollup 字段来判断命中的是哪一张 Rollup 表。
1. 名词解释
- Base: 基表
- Rollup: 一般指基于 Base 表创建的 Rollup 表,但在一些场景包括 Base 以及 Rollup 表。
2. 索引
3. 聚合数据
操作手册
数据导入
1. 导入总览
1.1 基本概念
- Frontend(FE):Doris 系统的元数据和调度节点。在导入流程中主要负责导入规划生成和导入任务的调度工作。
- Backend(BE):Doris 系统的计算和存储节点。在导入流程中主要负责数据的 ETL 和存储。
- Broker:Broker 为一个独立的无状态进程。封装了文件系统接口,提供 Doris 读取远端存储系统中文件的能力。
- 导入作业(Load job):导入作业读取用户提交的源数据,转换或清洗后,将数据导入到 Doris 系统中。导入完成后,数据即可被用户查询到。
- Label:所有导入作业都有一个 Label。Label 在一个数据库内唯一,可由用户指定或系统自动生成,用于标识一个导入作业。相同的 Label 仅可用于一个成功的导入作业。
- MySQL 协议/HTTP 协议:Doris 提供两种访问协议接口。 MySQL 协议和 HTTP 协议。部分导入方式使用 MySQL 协议接口提交作业,部分导入方式使用 HTTP 协议接口提交作业。
1.2 导入方式
- Broker load
- Stream load
- Insert
- Multi load
- Routine load
1.3 基本原理
1.3.1 导入执行流程
- PENDING(非必须)
- ETL(非必须)
- LOADING
- FINISHED
- CANCELLED
1.3.2 Label和原子性
1.4 同步和异步
1.4.1 同步
1.4.2 异步
1.4.3 注意事项
1.5 内存限制
1.6 最佳实践
1.7 通用系统配置
1.7.1 FE配置
1.7.2 BE配置
1.7.3 列映射
2.Broker Load
一种异步导入方式
2.1 适用场景
2.2 名词解释
2.3 基本原理
2.4 基本操作
2.4.1 创建导入
2.4.2 查看导入
2.4.3 取消导入
2.5 相关系统配置
2.5.1 FE 配置
2.6 最佳实践
2.6.1 应用场景
使用 Broker load 最适合的场景就是原始数据在文件系统(HDFS,BOS,AFS)中的场景。其次,由于 Broker load 是单次导入中唯一的一种异步导入的方式,所以如果用户在导入大文件中,需要使用异步接入,也可以考虑使用 Broker load。
2.6.2 数据量
这里仅讨论单个 BE 的情况,如果用户集群有多个 BE 则下面标题中的数据量应该乘以 BE 个数来计算。比如:如果用户有3个 BE,则 3G 以下(包含)则应该乘以 3,也就是 9G 以下(包含)。
2.6.3 性能分析
2.6.4 完整例子
2.7 常见问题
3. Stream load
Stream load 是一个同步的导入方式,用户通过发送 HTTP 协议发送请求将本地文件或数据流导入到 Doris 中。Stream load 同步执行导入并返回导入结果。用户可直接通过请求的返回体判断本次导入是否成功。
Stream load 主要适用于导入本地文件,或通过程序导入数据流中的数据。
3.1 基本原理
3.2 基本操作
3.3 相关系统配置
3.4 最佳实践
3.4.1 应用场景
使用 Stream load 的最合适场景就是原始文件在内存中,或者在磁盘中。其次,由于 Stream load 是一种同步的导入方式,所以用户如果希望用同步方式获取导入结果,也可以使用这种导入。
3.4.2 数据量
3.4.3 完整例子
3.5 常见问题
4. Routine Load
例行导入(Routine Load)功能为用户提供了一种自动从指定数据源进行数据导入的功能。
本文档主要介绍该功能的实现原理、使用方式以及最佳实践。
5. insert into
Insert Into 语句的使用方式和 MySQL 等数据库中 Insert Into 语句的使用方式类似。但在 Doris 中,所有的数据写入都是一个独立的导入作业。所以这里将 Insert Into 也作为一种导入方式介绍。
主要的 Insert Into 命令包含以下两种;
- INSERT INTO tbl SELECT …
- INSERT INTO tbl (col1, col2, …) VALUES (1, 2, …), (1,3, …);
其中第二种命令仅用于 Demo,不要使用在测试或生产环境中。
6. spark Load
Spark load 通过外部的 Spark 资源实现对导入数据的预处理,提高 Doris 大数据量的导入性能并且节省 Doris 集群的计算资源。主要用于初次迁移,大数据量导入 Doris 的场景。
Spark load 是一种异步导入方式,用户需要通过 MySQL 协议创建 Spark 类型导入任务,并通过 SHOW LOAD
查看导入结果。
6.1 适用场景
- 源数据在 Spark 可以访问的存储系统中,如 HDFS。
- 数据量在 几十 GB 到 TB 级别。
7. delete
Delete不同于其他导入方式,它是一个同步过程。和Insert into相似,所有的Delete操作在Doris中是一个独立的导入作业,一般Delete语句需要指定表和分区以及删除的条件来筛选要删除的数据,并将会同时删除base表和rollup表的数据。