provider.xml 505b 美国药品 法案?我我想不要了

分布式架构基础篇(基于Dubbo)(5)
一、提供方Provider
public interface DemoService {
String sayHello(String name);
public List getUsers();
public class DemoServiceImpl implements DemoService {
public String sayHello(String name) {
return &Hello & +
public List getUsers() {
List list = new ArrayList();
User u1 = new User();
u1.setName(&jack&);
u1.setAge(20);
u1.setSex(&m&);
User u2 = new User();
u2.setName(&tom&);
u2.setAge(21);
u2.setSex(&m&);
User u3 = new User();
u3.setName(&rose&);
u3.setAge(19);
u3.setSex(&w&);
list.add(u1);
list.add(u2);
list.add(u3);
* 实现序列化
* @author Administrator
public class User implements Serializable {
private static final long serialVersionUID = 1L;
public User() {
public User(int age, String name, String sex) {
this.age =
this.name =
this.sex =
public int getAge() {
public void setAge(int age) {
this.age =
public String getName() {
public void setName(String name) {
this.name =
public String getSex() {
public void setSex(String sex) {
this.sex =
4.applicationContenxt.xml配置
注意:提供方需要指定端口,不同提供方需要使用不同端口,不然会有端口冲突,使用的端口需要在防火墙iptables中配置允许通过
消费方不用指定端口
&?xml version=&1.0& encoding=&UTF-8&?&
&beans xmlns=&http://www.springframework.org/schema/beans&
xmlns:xsi=&http://www.w3.org/2001/XMLSchema-instance& xmlns:dubbo=&/schema/dubbo&
xsi:schemaLocation=&http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
/schema/dubbo
/schema/dubbo/dubbo.xsd
&!-- 提供方应用信息,用于计算依赖关系 --&
&dubbo:application name=&demo_provider& /&
&!-- 使用zookeeper注册中心暴露服务地址 --&
&dubbo:registry address=&zookeeper://192.168.1.121:2181& /&
&!-- 用dubbo协议在20880端口暴露服务 --&
&dubbo:protocol name=&dubbo& port=&20880& /&
&!-- 声明需要暴露的服务接口 --&
&dubbo:service interface=&com.unj.dubbotest.provider.DemoService&
ref=&demoService& /&
&!-- 具体的实现bean,用来注入--&
&bean id=&demoService& class=&com.unj.dubbotest.provider.impl.DemoServiceImpl& /&
&/beans&5.提供方Provider测试方法
public class Provider {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { &applicationContext.xml& });
context.start();
System.in.read(); // 为保证服务一直开着,利用输入流的阻塞来模拟
6.在Dubbo管理控制台上查看发布的提供方
二、消费方Consume
1.引用提供方的接口
2.消费方applictionContenxt.xml配置
&?xml version=&1.0& encoding=&UTF-8&?&
&beans xmlns=&http://www.springframework.org/schema/beans&
xmlns:xsi=&http://www.w3.org/2001/XMLSchema-instance& xmlns:dubbo=&/schema/dubbo&
xsi:schemaLocation=&http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
/schema/dubbo
/schema/dubbo/dubbo.xsd
&!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --&
&dubbo:application name=&hehe_consumer& /&
&!-- 使用zookeeper注册中心暴露服务地址 --&
&dubbo:registry address=&zookeeper://192.168.1.121:2181& /&
&!-- 生成远程服务代理,可以像使用本地bean一样使用demoService --&
&dubbo:reference id=&demoService&
interface=&com.unj.dubbotest.provider.DemoService& /&
&/beans&3.消费方调用提供方
public class Consumer {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { &applicationContext.xml& });
context.start();
DemoService demoService = (DemoService) context.getBean(&demoService&);
String hello = demoService.sayHello(&tom&);
System.out.println(hello);
List list = demoService.getUsers();
if (list != null && list.size() & 0) {
for (int i = 0; i & list.size(); i++) {
System.out.println(list.get(i));
System.in.read();
控制台信息输出
dubbo管理控制台查看
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:41602次
积分:1630
积分:1630
排名:第18083名
原创:121篇
(42)(67)(20)80239人阅读
Java(24)
Hadoop(3)
Dubbo是什么?
Dubbo是阿里巴巴SOA服务化治理方案的核心框架,每天为2,000+个服务提供3,000,000,000+次访问量支持,并被广泛应用于阿里巴巴集团的各成员站点。
Dubbo[]是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
其核心部分包含:
远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
Dubbo能做什么?
透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者。
Spring集成
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可,Dubbo基于Spring的Schema扩展进行加载。
Provider部分
新建一个Web工程 ,导入以下包:
新建一个DemoService.java类,提供者和消费者都要有这个Service,
package com.znn.
public interface DemoService {
String sayHello(String name);
新建一个服务提供方实现接口,对服务消费方隐藏实现
DemoServiceImpl.java
package com.znn.
public class DemoServiceImpl implements DemoService{
public String sayHello(String name) {
return &Hello Dubbo,Hello & +
用Spring配置声明暴露服务:
新建一个dubbo-provider.xml文件对dubbo进行配置:
&?xml version=&1.0& encoding=&UTF-8&?&
&beans xmlns=&http://www.springframework.org/schema/beans&
xmlns:xsi=&http://www.w3.org/2001/XMLSchema-instance&
xmlns:dubbo=&/schema/dubbo&
xsi:schemaLocation=&http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
/schema/dubbo
/schema/dubbo/dubbo.xsd&&
&!-- 提供方应用信息,用于计算依赖关系 --&
&dubbo:application name=&hello-world-app&
&!-- 使用multicast广播注册中心暴露服务地址 --&
&dubbo:registry address=&multicast://224.5.6.7:1234& /& --&
&!-- 使用zookeeper注册中心暴露服务地址 --&
&dubbo:registry address=&zookeeper://115.28.189.59:2181& /&
&!-- 用dubbo协议在20880端口暴露服务 --&
&dubbo:protocol name=&dubbo& port=&20880& /&
&!-- 声明需要暴露的服务接口 --&
&dubbo:service interface=&com.znn.provider.DemoService& ref=&demoService& /&
&!-- 和本地bean一样实现服务 --&
&bean id=&demoService& class=&com.znn.provider.DemoServiceImpl& /&
&/beans&注:有两种暴露地址的方法,广播的那个在测试消费者的时候没有成功,就自己搭了一个zookeeper,使用zookeeper来管理。
消费者部分
另外新建一个工程:znn-service-consumer
导入第三方包,和提供者一样。另外可以把提供者打成一个Jar,在消费者部分添加,如果用的是Eclipse并且提供者和消费者在同一个workplace下,可以直接通过右键-&propertities-&Java Build Path-&Project-&Add来添加。
新建一个dubbo-consumer.xml,对dubbo进行配置:
&?xml version=&1.0& encoding=&UTF-8&?&
&beans xmlns=&http://www.springframework.org/schema/beans&
xmlns:xsi=&http://www.w3.org/2001/XMLSchema-instance&
xmlns:dubbo=&/schema/dubbo&
xsi:schemaLocation=&http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
/schema/dubbo
/schema/dubbo/dubbo.xsd&&
&!-- 消费方应用名,用于计算依赖关系,不是匹配条件,不要与提供方一样 --&
&dubbo:application name=&consumer-of-helloworld-app&/&
&!-- 使用multicast广播注册中心暴露发现服务地址 --&
&!-- &dubbo:registry address=&multicast://224.5.6.7:1234& /& --&
&dubbo:registry address=&zookeeper://115.28.189.59:2181& /&
&!-- 生成远程服务代理,可以和本地bean一样使用demoService --&
&dubbo:reference id=&demoService& interface=&com.znn.provider.DemoService& /&
新建一个测试类,当然具体用的时候会写一个专门的类使用IOC注入:
package com.znn.
import org.springframework.context.support.ClassPathXmlApplicationC
import com.znn.provider.DemoS
public class Consumer {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(&file:D:/develop/myeclipsework/znn-service-consumer/WebRoot/WEB-INF/dubbo-consumer.xml&);
context.start();
DemoService demoService = (DemoService)context.getBean(&demoService&); // 获取远程服务代理
String hello = demoService.sayHello(&world&); // 执行远程方法
System.out.println(hello);
先运行提供者,然后运行消费者:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:580941次
积分:5565
积分:5565
排名:第3289名
原创:68篇
转载:56篇
评论:100条
(5)(1)(1)(1)(1)(1)(2)(2)(1)(2)(1)(1)(4)(4)(1)(4)(6)(2)(4)(1)(3)(2)(1)(1)(2)(1)(4)(10)(2)(12)(31)(8)(4)9691人阅读
学习笔记(426)
语法(SYNTAX):
&providerandroid:=&list&
& & & & & android:=[&true&
| &false&]
& & & & & android:=[&true&
| &false&]
& & & & & android:=[&true&
| &false&]
& & & & & android:=&drawable
& & & & & android:=&integer&
& & & & & android:=&string
& & & & & android:=[&true&
| &false&]
& & & & & android:=&string&
& & & & & android:=&string&
& & & & & android:=&string&
& & & & & android:=&string&
& & & & & android:=[&true&
| &false&]
& & & & & android:=&string&&
&/provider&
被包含于(CONTAINED IN):
&application&
能够包含的元素(CAN CONTAIN):
&meta-data&
&grant-uri-permission&
&path-permission&
说明(DESCRIPTION):
这个元素用于声明一个内容提供器,它对应一个ContentProvider类的子类。它提供了对由应用程序管理的数据的结构化的访问。应用程序使用的所有的内容提供器都必须在其清单文件中由&provider&元素来声明。对于那些没有在清单中声明的内容提供器,系统看不到,也不会运行。(只需要声明那些作为该应用程序一部分的来开发的那些内容提供器,而不是那些由应用程序使用的由其他人开发本应用之外的那些内容提供器)。
Android系统通过content:URI的授权部分来识别内容提供器。例如,假设下列的URI要传递给ContentResolver.query()方法:
content://com.example.project.healthcareprovider/nurses/rn
content:表示数据是属于内容提供器的,授权(com.example.project.healthcareprovider):标识着一个具体的提供器。因此授权必须唯一的。通常,就像这个例子一样,授权是一个完整的ContentProvider子类的名称。URI的路径部分被内容提供器用来标识具体的数据子集,但是那些路径没有被声明在清单文件中。
关于使用可开发内容提供器的更多信息,请看内容提供的文档()。
属性(ATTRIBUTES):
android:authorities
标识内容提供器范围内的数据URI的授权列表,有多个授权时,要用分号来分离每个授权。为了避免冲突,授权名应该使用Java样式的命名规则(如:com.example.provider.cartoonprovider)。通常,用ContentProvider子类名称来设定这个属性。
这个属性没有默认值,至少要指定一个授权。
android:enabled
这个属性用于指定这个内容提供器是否能够被系统安装。设置为true,则可以安装;否则不能安装。默认值是true。
&application&元素有它自己的enabled属性,这个属性会应用给所有的应用程序组件,包括内容提供器。&application&和&provider&的enabled属性都必须设置为true(它们的默认值都是true)。如果有一个设置为false,那么提供器就被禁止安装。
android:exported
这个属性用于指定该内容提供器是否能够被其他的应用程序组件使用。如果设置为true,则可以被使用,否则不能被使用。如果设置为false,该提供器只对同名的应用程序或有相同用户ID的应用程序有效。默认值是true。
虽然能够使用这个属性来公开内容提供器,但是依然还要用permission属性来限制对它的访问。
android:grantUriPermission
这个属性用于设定那些对内容提供的数据没有访问权限的访问者,是否能够被授予访问的权限,这个权限是临时性的,它会克服由readPermission、writePermission和permission属性的设置限制。如果这个属性设置为true,那么权限就可以授予访问者,否则不会授予没有访问权限的访问者。如果设置为true,则权限可以临时被授予内容提供器的任何数据。如果设置为false,则权限只能被授予&gran-uri-permission&子元素中所列出的数据子集。默认值是false。
这种授权是应用程序提供了一种一次性访问被权限所保护的数据的方法。例如,当一个e-mail包含了一个附件时,mail应用程序就可以调用适当的浏览器来打开附件,即使这个浏览器没有查看所有内容提供器数据的权限。
在这样的场景中,权限是通过激活组件的Intent对象中的FLAG_GRANT_READ_URI_PERMISSION和FLAG_GRANT_WRITE_URI_PERMISSION标记来授予的。例如,mail应用程序可以把FLAG_GRANT_READ_URI_PERMISSION标记放到传递给Context.startActivity()方法的Intent参数中。这样权限就被授予了Intent对象中所指定的URI。
如果要启用这个功能,既可以通过把这个属性设置为true来完成,也可以通过定义&grant-uri-permission&子元素来实现,在切换RUI时,必须调用Context.revokeUriPermission()方法从提供器把权限删除。
android:icon
这个属性用于定义一个代表内容提供器的图标。它必须要引用一个包含图片定义的可绘制资源。如果这个属性没有设置,那么就会使用应用程序的&application&元素的icon属性值来代替。
android:initOrder
这个属性用于定义内容提供器应该被实例化的顺序,这个顺序是相对与相同进程所拥有的其他内容提供器的。当内容提供器间有相互的依赖时,就需要设置这个属性,以确保它们能够按照其依赖的顺序被创建。这个属性值是一个简单的整数,大的数字要被优先初始化。
Android:label
这个属性用于给内容提供器定义一个用户可读的标签。如果这个属性没有设置,那么它会使用&application&元素的label属性值来代替。
这个标签应该引用一个字符串资源来设置,以便它能够像其他的用户界面中的字符串一样被本地化。但是为了方便应用程序的开发,也能够使用原生的字符串来设置这个属性,但正式发布时一定要引用字符串资源。
android:multiprocess
这个属性用于设定是否能够在每个使用该内容提供器的客户端进程中都创建一个内容提供器的实例,如果设置为true,这个能够在其每个客户端进程中创建一个实例,否则不可以。默认值是false。
通常,内容提供器是在定义它的应用程序的进程中被实例化的。但是,如果这个属性被设置为true,系统就能够在每个要与该内容提供器进行交互的客户端进程中创建一个内容提供器的实例,这样就避免了进程间通信的开销。
android:name
这个属性用于定义内容提供器的实现类的名称,它是ContentProvider类的一个子类。这个属性应该使用完整的Java类名来设定(如:com.example.project.TransportationProvider)。但是也可以使用简写(如:.TransporttationProvider),这时系统会使用&manifest&元素中指定的包名跟这个简写名称的组合来识别内容提供器。
这个属性没有默认值,必须要给这个属性设定一个名称。
android:permission
这个属性用于设定客户端在读写内容提供器的数据时必须要有的权限的名称。这个属性为同时设置读写权限提供了一种便利的方法。但是readPermission和writePermission属性的优先级要比这个属性高。如果readPermission属性也被设置了,那么它就会控制对内容提供器的查询访问。如果writePermission属性被设置,它就会控制对内容提供器数据的修改访问。
android:process
这个属性用于定义内容提供器应该运行在那个进程中的进程名称。通常,应用程序的所有组件都运行在给应用程序创建的默认进程中。它有与应用程序包相同的名称。&application&元素的process属性能够给其所有的组件设置一个不同的默认进程。但是每个组件都能够用它们自己的process属性来覆盖这个默认设置,从而允许把应用程序分离到不同的多个进程中。
如果这个属性值是用“:”开头的,那么在需要这个提供器的时候,系统就会给这个应用程序创建一个新的私有进程,并且对应的Activity也要运行在那个私有进程中。如果用小写字母开头,那么Activity则会运行在一个用这个属性值命名的全局进程中,它提供了对内容提供器的访问权限。这样就允许不同应用程序的组件能够共享这个进程,从而减少对系统资源的使用。
android:readPermission
这个属性用于设置查询内容提供器的数据时,客户端所必须要有的权限。
android:syncable
这个属性用于设定内容提供器控制下的数据是否要与服务器上的数据进行同步,如果设置为true,则要同步,否则不需要同步。
android:writePermission
这个属性用于设置修改内容提供器的数据时,客户端所必须要有的权限。
被引入的版本(INTRODUCED IN):
API Level 1
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1764183次
积分:22518
积分:22518
排名:第218名
原创:85篇
转载:14篇
译文:443篇
评论:185条
(5)(10)(1)(2)(6)(2)(1)(1)(4)(5)(4)(7)(7)(5)(8)(5)(2)(4)(7)(2)(16)(27)(25)(19)(15)(12)(13)(25)(54)(49)(55)(67)(3)(3)(9)(4)(15)(12)(27)(3)转载请注明出处:http://blog.csdn.net/l/article/details/不多说,不废话,直接上代码,大家一看都懂得/**
* ContentProvider操作XML文件的封装示例方法
* @author liuyazhuang
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SharedPreferences sp= getContext().getSharedPreferences(&pim_preferences&, Context.MODE_PRIVATE);
String[] columns = new String[] { &_id&, &url&, &target& };
MatrixCursor stringCursor = new MatrixCursor(columns);
String row[] = new String[3];
row[0] = &1&;
row[1] = sp.getString(&url&, &&);
row[2] = sp.getString(&target&, &&);
stringCursor.addRow(row);
return stringC
版权声明:本文为博主原创,未经博主允许不得转载。Github Mybatis深入学习之XML配置 - 博客频道 - CSDN.NET
君子必著于信,而后人信之
一点改变,日积月累,结果大不相同
分类:Mybatis
& & & & &MyBatis的配置的灵活性也是其一大亮点,这样适合不同的配置需求,显得其有包涵有涵养。所以,这也是许多公司选择它作为持久化框架的原因。
原文地址:
MyBatis包含了设置和属性来影响MyBatis的行为,其高级文档结构如下:
configuration
(属性)(配置)(类型别名)(类型处理器)(对象工厂)(插件)(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)dataSource(数据源)
(数据库ID提供者)(映射器)
下面就来一一作一介绍:
1、属性(properties)
& & & &属性都是可以配置在一个典型的Java属性文件实例,通过属性元素的子元素或通过外部化,拥有可替换的特点。例如:
&properties resource=&org/mybatis/example/config.properties&&
&property name=&username& value=&dev_user&/&
&property name=&password& value=&F2Fa3!33TYyg&/&
&/properties&
然后,可以使用这些属性可以在整个配置文件值来替换,需要进行动态配置。例如:
type=&POOLED&
name=&driver& value=&${driver}&
name=&url& value=&${url}&
name=&username& value=&${username}&
name=&password& value=&${password}&
在属性元素设置的值,在这个例子中的用户名和密码将被替换。 config.properties文件中包含的值将被替换的驱动程序和URL属性。这提供了很多配置的选项。
属性也可以被传递到SqlSessionBuilder.build()方法。例如:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, props);
// ... or ...
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment, props);
如果一个属性存在于一个以上的这些地方的MyBatis的加载按以下顺序(属性文件配置加载顺序):
(1)、在body内优先读取指定的属性的属性元素。
(2)、从classpath资源或URL属性的属性元素加载的属性第二被读取,并覆盖已经指定的任何重复的属性,
(3)、传递的属性作为方法参数最后被读取,并覆盖任何重复的特性,可能已加载从属性的身体和资源/ url属性。
因此,最高优先级的属性是那些作为方法参数,其次是资源/ url属性,最后指定的属性的属性元素在body内传递。
2、配置(setting)
& & & & 配置是极其重要的调整,以及在运行时修改的MyBatis的行为方式。下表描述的是它们的设置、含义和默认值。
Setting(设置)
Description(描述)
Valid&Values(验证值组)
Default(默认值)
cacheEnabled
在全局范围内启用或禁用缓存配置任何映射器在此配置下。
true&|&false
lazyLoadingEnabled
在全局范围内启用或禁用延迟加载。禁用时,所有协会将热加载。
true&|&false
aggressiveLazyLoading
启用时,有延迟加载属性的对象将被完全加载后调用懒惰的任何属性。否则,每一个属性是按需加载。
true&|&false
multipleResultSetsEnabled
允许或不允许从一个单独的语句(需要兼容的驱动程序)要返回多个结果集。
true&|&false
useColumnLabel
使用列标签,而不是列名。在这方面,不同的驱动有不同的行为。参考驱动文档或测试两种方法来决定你的驱动程序的行为如何。
true&|&false
useGeneratedKeys
允许JDBC支持生成的密钥。兼容的驱动程序是必需的。此设置强制生成的键被使用,如果设置为true,一些驱动会不兼容性,但仍然可以工作。
true&|&false
autoMappingBehavior
指定MyBatis的应如何自动映射列到字段/属性。NONE自动映射。&PARTIAL只会自动映射结果没有嵌套结果映射定义里面。&FULL会自动映射的结果映射任何复杂的(包含嵌套或其他)。
NONE,&PARTIAL,&FULL
defaultExecutorType
配置默认执行人。SIMPLE执行人确实没有什么特别的。&REUSE执行器重用准备好的语句。&BATCH执行器重用语句和批处理更新。
SIMPLE&REUSE&BATCH
defaultStatementTimeout
设置驱动程序等待一个数据库响应的秒数。
Any&positive&integer
Not&Set&(null)
safeRowBoundsEnabled
允许使用嵌套的语句RowBounds。
true&|&false
mapUnderscoreToCamelCase
从经典的数据库列名A_COLUMN启用自动映射到骆驼标识的经典的Java属性名aColumn。
true&|&false
localCacheScope
MyBatis的使用本地缓存,以防止循环引用,并加快反复嵌套查询。默认情况下(SESSION)会话期间执行的所有查询缓存。如果localCacheScope=STATMENT本地会话将被用于语句的执行,只是没有将数据共享之间的两个不同的调用相同的SqlSession。
SESSION&|&STATEMENT
jdbcTypeForNull
指定为空值时,没有特定的JDBC类型的参数的JDBC类型。有些驱动需要指定列的JDBC类型,但其他像NULL,VARCHAR或OTHER的工作与通用值。
JdbcType&enumeration.&Most&common&are:&NULL,&VARCHAR&and&OTHER
lazyLoadTriggerMethods
指定触发延迟加载的对象的方法。
A&method&name&list&separated&by&commas
equals,clone,hashCode,toString
defaultScriptingLanguage
指定所使用的语言默认为动态SQL生成。
A&type&alias&or&fully&qualified&class&name.
org.apache.ibatis.scripting.xmltags.XMLDynamicLanguageDriver
callSettersOnNulls
指定如果setter方法或地图的put方法时,将调用检索到的值是null。它是有用的,当你依靠Map.keySet()或null初始化。注意原语(如整型,布尔等)不会被设置为null。
true&|&false
指定的前缀字串,MyBatis将会增加记录器的名称。
Any&String
指定MyBatis的日志实现使用。如果此设置是不存在的记录的实施将自动查找。
SLF4J&|&LOG4J&|&LOG4J2&|&JDK_LOGGING&|&COMMONS_LOGGING&|&STDOUT_LOGGING&|&NO_LOGGING
proxyFactory
指定代理工具,MyBatis将会使用创建懒加载能力的对象。
CGLIB&|&JAVASSIST
下面是一个完整的setting元素的设置示例:
&settings&
&setting name=&cacheEnabled& value=&true&/&
&setting name=&lazyLoadingEnabled& value=&true&/&
&setting name=&multipleResultSetsEnabled& value=&true&/&
&setting name=&useColumnLabel& value=&true&/&
&setting name=&useGeneratedKeys& value=&false&/&
&setting name=&autoMappingBehavior& value=&PARTIAL&/&
&setting name=&defaultExecutorType& value=&SIMPLE&/&
&setting name=&defaultStatementTimeout& value=&25&/&
&setting name=&safeRowBoundsEnabled& value=&false&/&
&setting name=&mapUnderscoreToCamelCase& value=&false&/&
&setting name=&localCacheScope& value=&SESSION&/&
&setting name=&jdbcTypeForNull& value=&OTHER&/&
&setting name=&lazyLoadTriggerMethods& value=&equals,clone,hashCode,toString&/&
&/settings&
3、类型别名
& & A型别名是一个用较短的名称代表Java类型。这是唯一的XML配置有关,只是存在,以减少多余的类完全限定名。例如:
&typeAliases&
&typeAlias alias=&Author& type=&domain.blog.Author&/&
&typeAlias alias=&Blog& type=&domain.blog.Blog&/&
&typeAlias alias=&Comment& type=&ment&/&
&typeAlias alias=&Post& type=&domain.blog.Post&/&
&typeAlias alias=&Section& type=&domain.blog.Section&/&
&typeAlias alias=&Tag& type=&domain.blog.Tag&/&
&/typeAliases&
根据这样的结构,Blog现在可以在任何地方使用,domain.blog.Blog可能。您也可以指定包MyBatis将会搜索bean。例如:
&typeAliases&
&package name=&domain.blog&/&
&/typeAliases&
每个bean在domain.blog发现,如果没有找到注解,将注册使用小写开头的非限定类的bean的名称作为别名。临屋区将为domain.blog.Author被登记author。 如果@Alias注解的发现,它的值将被用作别名。请看下面的例子:
@Alias(&author&)
public class Author {
有许多内置类型,对于普通的Java类型的别名。他们都是大小写不敏感,注意由于重载的名字需要特殊处理原语。
Mapped&Type
BigDecimal
bigdecimal
BigDecimal
collection
Collection
4、类型处理器
& & & & 每当MyBatis的一个PreparedStatement对象,或设置一个参数,从ResultSet检索值(一个类型处理器的用于检索的价值),来匹配Java类型。下面的表格描述了默认的类型处理器。
Type&Handler
Java&Types
JDBC&Types
BooleanTypeHandler
java.lang.Boolean,&boolean
Any&compatible&BOOLEAN
ByteTypeHandler
java.lang.Byte,&byte
Any&compatible&NUMERIC&or&BYTE
ShortTypeHandler
java.lang.Short,&short
Any&compatible&NUMERIC&or&SHORT&INTEGER
IntegerTypeHandler
java.lang.Integer,&int
Any&compatible&NUMERIC&or&INTEGER
LongTypeHandler
java.lang.Long,&long
Any&compatible&NUMERIC&or&LONG&INTEGER
FloatTypeHandler
java.lang.Float,&float
Any&compatible&NUMERIC&or&FLOAT
DoubleTypeHandler
java.lang.Double,&double
Any&compatible&NUMERIC&or&DOUBLE
BigDecimalTypeHandler
java.math.BigDecimal
Any&compatible&NUMERIC&or&DECIMAL
StringTypeHandler
java.lang.String
CHAR,&VARCHAR
ClobTypeHandler
java.lang.String
CLOB,&LONGVARCHAR
NStringTypeHandler
java.lang.String
NVARCHAR,&NCHAR
NClobTypeHandler
java.lang.String
ByteArrayTypeHandler
Any&compatible&byte&stream&type
BlobTypeHandler
BLOB,&LONGVARBINARY
DateTypeHandler
java.util.Date
DateOnlyTypeHandler
java.util.Date
TimeOnlyTypeHandler
java.util.Date
SqlTimestampTypeHandler
java.sql.Timestamp
SqlDateTypeHandler
java.sql.Date
SqlTimeTypeHandler
java.sql.Time
ObjectTypeHandler
OTHER,&or&unspecified
EnumTypeHandler
Enumeration&Type
VARCHAR&any&string&compatible&type,&as&the&code&is&stored&(not&index).
EnumOrdinalTypeHandler
Enumeration&Type
Any&compatible&NUMERIC&or&DOUBLE,&as&the&position&is&stored&(not&the&code&itself).
你可以重写类型处理器或创建您自己处理不支持的或非标准的类型。要做到这一点,只需继承org.apache.ibatis.type.BaseTypeHandler类和选择性映射新的类型处理器类的JDBC类型。例如:
// ExampleTypeHandler.java
@MappedJdbcTypes(JdbcType.VARCHAR)
public class ExampleTypeHandler extends BaseTypeHandler&String& {
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, parameter);
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
return rs.getString(columnIndex);
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
&!-- mybatis-config.xml --&
&typeHandlers&
&typeHandler handler=&org.mybatis.example.ExampleTypeHandler&/&
&/typeHandlers&
使用这样的类型处理器将会覆盖已经存在的Java的String类型属性和VARCHAR参数及结果的类型处理器。注意MyBatis不会审视数据库元数据的类型来确定的,所以你必须指定,这是一个VARCHAR字段的参数和结果映射到正确的类型处理。由于这样事实是MyBatis直到执行该语句,它不知道的数据类型。
MyBatis会知道你要处理这个类型处理器通过内省其泛型类型的Java类型,但你可以重写此行为由两个手段:
(1)、添加javaType属性类型处理器的元素(例如:javaType=“String”)
(2)、添加注释类型处理器类指定Java类型的列表,将它与一个@ MappedTypes。此注释将被忽略,如果javaType属性也被指定。
通过下列两种方式可以指定相关联的JDBC类型:
(1)、添加jdbcType属性到类型处理器的元素(例如:jdbcType为VARCHAR)。
(2)、添加注释联想到指定的JDBC类型列表类型处理器类的一个@ MappedJdbcTypes。此注释将被忽略,而jdbcType属性将被指定。
最后你可以让MyBatis搜索你的类型处理器:
&!-- mybatis-config.xml --&
&typeHandlers&
&package name=&org.mybatis.example&/&
&/typeHandlers&
注意当使用JDBC类型自动发现功能时只能被指定的注解。
您可以创建一个能够处理超过一个类通用的的类型处理器。为了这个目的,MyBatis将会作为参数,并通过实际的类构造类型处理器时,接收类添加一个构造。
//GenericTypeHandler.java
public class GenericTypeHandler&E extends MyObject& extends BaseTypeHandler&E& {
private Class&E&
public GenericTypeHandler(Class&E& type) {
if (type == null) throw new IllegalArgumentException(&Type argument cannot be null&);
this.type =
EnumTypeHandler EnumOrdinalTypeHandler泛型类型处理器。在下一节中,我们将了解他们。
5、处理枚举(Handling Enums)
如果你想使用枚举映射,你会需要使用EnumTypeHandler或EnumOrdinalTypeHandler。
例如,让我们说,我们需要存储的舍入模式,应该用一些数字,如果它需要四舍五入。默认情况下,的MyBatis使用EnumTypeHandler的的枚举值转换成自己的名字。
注意:EnumTypeHandler是在这个意义特殊,它不像其他的处理程序,它并不只是处理一个特定的类,而是任何扩展Enum的类。
然而,我们可能不希望存储姓名。我们的DBA可能整数代码,而不是坚持。这是一样容易:添加EnumOrdinalTypeHandler到你的配置文件的类型处理器,现在每个与RoundingMode将被映射到一个整数,使用其序数值。
&!-- mybatis-config.xml --&
&typeHandlers&
&typeHandler handler=&org.apache.ibatis.type.EnumOrdinalTypeHandler& javaType=&java.math.RoundingMode&/&
&/typeHandlers&
但是如果你想相同的枚举映射到一个字符串在一个地方和另一个整数?自动映射器会自动使用EnumOrdinalTypeHandler的,所以如果我们想回去使用简单陈旧的普通EnumTypeHandler的,我们必须告诉它,明确设置类型处理程序使用这些SQL语句。(映射文件没有涉及到下一节,因此,如果这是你第一次通过阅读文档,你可能现在想跳过这些但以后会回来了解它的。)
&!DOCTYPE mapper
PUBLIC &-//mybatis.org//DTD Mapper 3.0//EN&
&http://mybatis.org/dtd/mybatis-3-mapper.dtd&&
&!DOCTYPE mapper
PUBLIC &-//mybatis.org//DTD Mapper 3.0//EN&
&http://mybatis.org/dtd/mybatis-3-mapper.dtd&&
&mapper namespace=&org.apache.ibatis.submitted.rounding.Mapper&&
&resultMap type=&org.apache.ibatis.submitted.rounding.User& id=&usermap&&
&id column=&id& property=&id&/&
&result column=&name& property=&name&/&
&result column=&funkyNumber& property=&funkyNumber&/&
&result column=&roundingMode& property=&roundingMode&/&
&/resultMap&
&select id=&getUser& resultMap=&usermap&&
select * from users
&insert id=&insert& parameterType=&org.apache.ibatis.submitted.rounding.User&&
insert into users (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode}
&resultMap type=&org.apache.ibatis.submitted.rounding.User& id=&usermap2&&
&id column=&id& property=&id&/&
&result column=&name& property=&name&/&
&result column=&funkyNumber& property=&funkyNumber&/&
&result column=&roundingMode& property=&roundingMode& typeHandler=&org.apache.ibatis.type.EnumTypeHandler&/&
&/resultMap&
&select id=&getUser2& resultMap=&usermap2&&
select * from users2
&insert id=&insert2& parameterType=&org.apache.ibatis.submitted.rounding.User&&
insert into users2 (id, name, funkyNumber, roundingMode) values (
#{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
请注意,这迫使我们使用一个resultMap代替resultType在select语句中。
6、对象工厂(objectFactory)
& & &MyBatis每次创建结果对象的一个新实例,它使用一个ObjectFactory实例实现。默认的ObjectFactory做多一点的实例化目标类的默认构造函数,或一个参数的构造函数,如果存在参数映射。如果你想重写默认的ObjectFactory,你可以创建你自己的。例如:
// ExampleObjectFactory.java
public class ExampleObjectFactory extends DefaultObjectFactory {
public Object create(Class type) {
return super.create(type);
public Object create(Class type, List&Class& constructorArgTypes, List&Object& constructorArgs) {
return super.create(type, constructorArgTypes, constructorArgs);
public void setProperties(Properties properties) {
super.setProperties(properties);
&!-- mybatis-config.xml --&
&objectFactory type=&org.mybatis.example.ExampleObjectFactory&&
&property name=&someProperty& value=&100&/&
&/objectFactory&
ObjectFactory接口非常简单。它包含两个创建方法,处理默认构造函数和其他处理带参数构造。最后,setProperties方法可以被用来配置ObjectFactory的。body内的objectFactory元素中定义的属性将被传递给setProperties方法的ObjectFactory实例在初始化之后。
7、插件(plugins)
& & MyBatis允许你调用映射语句的执行范围内的某些点拦截。默认情况下,MyBatis允许使用插件来拦截方法调用:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
这些类中方法的详细看签名,每一个完整的方法,可用每个MyBatis的释放与源代码来被发现。你应该理解你覆盖方法的行为,假设你正在做的事情不仅仅是监控呼叫。如果您尝试修改或覆盖一个给定的方法的行为,你很可能会打破MyBatis的核心。这是低层次的类和方法,所以使用插件持谨慎态度。
使用插件是非常简单的,通过他们所提供的力量。简单实现拦截器接口,确保你想拦截的指定签名。
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = &update&,
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
public Object plugin(Object target) {
return Plugin.wrap(target, this);
public void setProperties(Properties properties) {
&!-- mybatis-config.xml --&
&plugin interceptor=&org.mybatis.example.ExamplePlugin&&
&property name=&someProperty& value=&100&/&
&/plugins&
该插件在上面会拦截所有叫做“update”的方法在Executor实例,这是一个内部负责低层次映射语句的执行对象。
注意重写配置类:除了修改MyBatis核心的行为插件,你也可以完全覆盖配置类。简单扩展并覆盖任何方法里面,并把它传递到的sqlSessionFactoryBuilder.build(myConfig)方法的调用。再次提醒,虽然这可能会严重影响MyBatis的行为,所以谨慎使用
8、environments标签
& & & &MyBatis的可以配置多个environment。这是有助于您应用到多个数据库的SQL映射为任意数量的原因。例如,你可能会为您的开发,测试和生产环境中有不同的配置。或者,你可能有多个生产数据库,共享相同的架构,你想使用相同的SQL映射。有许多用例。
& & & &一个重要的事情要记住,虽然:虽然您可以配置多种environment,但你只能选择一个SqlSessionFactory对应的实例。
所以,如果你想连接两个数据库,你需要SqlSessionFactory的创建两个实例。三个数据库,你需要三个实例,并依此类推。这真的很容易,要记住:
每个数据库对应一个SqlSessionFactory
要指定创建哪种环境,你只需把它传递到SqlSessionFactoryBuilder作为一个可选的参数。接受环境中的两个签名是:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader, environment,properties);
如果环境被省略,则加载默认的环境,如下所示:
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader);
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(reader,properties);
environment元素定义了如何配置环境:
&environments default=&development&&
&environment id=&development&&
&transactionManager type=&JDBC&&
&property name=&...& value=&...&/&
&/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&
请注意这里的关键部分:
(1)、默认的环境ID(例如,default=“development”)。
(2)、的环境ID为每个定义的环境(例如:ID=“development”)。
(3)、事务管理器的配置(如type=“JDBC”)
(4)、数据源配置(例如type=“POOLED”)
默认的环境和环境ID是自我解释。将它们命名为任何你喜欢的,只是确保默认匹配其中之一。
事务管理(transactionManager)
TransactionManager的有两种类型(即类型=“[JDBC|MANAGED]”)中所包含的MyBatis:
JDBC - 这个配置直接简单使用了JDBC的提交和回滚。它依赖于从数据源的连接来管理事务范围。
MANAGED - 这个配置几乎没做什么。它从来不提交或回滚一个连接。相反,它让容器管理的整个生命周期的交易(如JEE应用服务器的上下文)。默认情况下,它会关闭连接。然而一些容器并没有想到这一点,因此如果你需要阻止它关闭连接,设置“形象尤其”属性设置为false。例如:
&transactionManager type=&MANAGED&&
&property name=&closeConnection& value=&false&/&
&/transactionManager&
注意:如果你打算使用MyBatis与Spring是无需配置任何的TransactionManager,因为Spring模块将覆盖任何先前设置的配置设置自己的。
TransactionManager的类型都不需要任何属性。但是,它们都是类型别名,所以换句话说,你可以把你自己的完全合格的类名或类型别名,是指你自己的实施的TransacFactory接口,而不是使用它们。
public interface TransactionFactory {
void setProperties(Properties props);
Transaction newTransaction(Connection conn);
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
任何在XML中配置的属性将被传递给setProperties()方法实例化后。您的实现还需要创建一个交易执行,这也是一个非常简单的接口:
public interface Transaction {
Connection getConnection() throws SQLE
void commit() throws SQLE
void rollback() throws SQLE
void close() throws SQLE
使用这两个接口,你可以完全自定义MyBatis对事务的处理。
dataSourcedataSource元素源配置使用标准的JDBC DataSource接口的JDBC连接对象。
许多MyBatis的应用程序配置数据源的例子中。然而,它不是必需的。虽然实现,为了方便使用延迟加载,这个数据源是必需的。有三种内建的数据源类型(即type=“?”):
(1)、NNPOOLED
UNPOOLED - 这个数据源的实现简单地打开和关闭连接每次请求。虽然它慢一点,这是一个不错的选择,简单的应用程序,不需要及时的可用连接的性能。在此表演区,不同的数据库也不同,所以对一些人来说可能是不太重要的游泳池,这个配置将是理想的。 UNPOOLED的DataSource配置只有五个属性:
driver - 这是完全合格的Java类的JDBC驱动程序(不DataSource类,如果你的驱动器包括一个)。
url - 这是你的数据库实例的JDBC URL。
username- 数据库的用户名登录。
password - 数据库的密码登录。
defaultTransactionIsolationLevel - 默认的事务隔离级别连接。
或者,您可以通过数据库驱动程序的属性。要做到这一点,与驱动程序前缀的属性,例如:
driver.encoding=UTF8
这将传递属性编码,UTF8的价值,数据库驱动程序通过的DriverManager.getConnection(URL,driverProperties)方法。
(2)、POOLED
& & &POOLED- 此实现数据源连接池的JDBC连接对象,以避免初始连接和认证所需的时间来创建一个新的连接实例。这是一个流行的做法,并发Web应用程序,以实现最快的响应。除了向(UNPOOLED)上述物业,也有许多其他属性,可用于配置POOLED的数据源:
poolMaximumActiveConnections - 这是有效的(即在使用)的连接,可以在任何给定时间存在的数量。默认值:10
poolMaximumIdleConnections - 的空闲连接的数目,可以存在于任何给定的时间。
poolMaximumCheckoutTime - 在一个连接可以“检查”出来之前,池中将被强制返回的时间量。默认值:20000 (即20秒)
poolTimeToWait - 这是一个低级别设置给连接池一个打印日志状态,并重新尝试连接的情况下,它采取不同寻常的长(避免失败永远默默地如果池配置错误)收购的机会。默认值:20000 (即20秒)
poolPingQuery - 平安查询发送到数据库以验证连接处于良好的工作秩序,并准备好接受请求。默认值是“ NO PING QUERY SET ” ,这将导致大多数的数据库驱动程序失败,并一个体面错误消息。
poolPingEnabled - 这是开启或禁用侦测查询。如果启用,则还必须与一个有效的SQL语句(最好是很快速的)设置poolPingQuery属性。默认值:false 。
poolPingConnectionsNotUsedFor - 此配置的频率将被用于poolPingQuery , 。这可以被设置匹配标准的数据库连接超时,以避免不必要的侦测。默认值:0 (即所有的连接都被侦测每一次 - 但只有当poolPingEnabled的当然是真实的) 。
(3)、JNDI
JNDI - 这个数据源的实现是为了使用如EJB容器或应用服务器,可以配置数据源,集中或外部的引用放到一个JNDI上下文。这个数据源配置只需要两个属性:
initial_context - 这个属性是用于上下文查找的InitialContext(即(initial_context)initialContext.lookup)。此属性是可选的,如果省略,然后将一个数据源属性抬头,直接以初始。
data_source - 这是可以找到参考的数据源实例的上下文路径。这将是抬头对返回的环境由initial_context的查找,或直接initial_context没有反对的InitialContext。
其他数据源配置相似,它可以直接向初始属性发送与环保这些属性的前缀,例如:
env.encoding=UTF8
这将发送的财产编码与UTF8实例化后的InitialContext的构造的价值。
9、数据库ID提供者(databaseIdProvider)
& & & MyBatis的是能够执行不同的语句,这取决于您的数据库供应商。多数据库厂商的支持的基础上的映射语句的databaseID的属性。 MyBatis将会加载与没有databaseId属性相匹配的当前用的databaseID的所有陈述。如果情况,如果发现相同的语句与和的databaseID无后者将被丢弃。为了使多厂商支持的MyBatis-config.xml文件中添加databaseIdProvider如下:
&databaseIdProvider type=&DB_VENDOR& /&
DB_VENDOR实施databaseIdProvider套作为databaseId由DatabaseMetaData:#getDatabaseProductName()返回的字符串。通常字符串太长,不同版本的同一产品也返回不同的值,所以你可能想将它翻译成一个较短的添加属性,比如如下:
&databaseIdProvider type=&DB_VENDOR&&
&property name=&SQL Server& value=&sqlserver&/&
&property name=&DB2& value=&db2&/&
&property name=&Oracle& value=&oracle& /&
&/databaseIdProvider&
当属性类型为DB_VENDOR databaseIdProvider搜索发现在返回的数据库产品名称或“空”的第一个关键,如果没有匹配的属性对应的属性值。在这种情况下,如果getDatabaseProductName()返回“甲骨文公司(DataDirect产品)”的的databaseID将被设置为“oracle”。
您可以建立您的自己的DatabaseIdProvider,实现接口org.apache.ibatis.mapping.DatabaseIdProvider的MyBatis-config.xml中注册:
public interface DatabaseIdProvider {
void setProperties(Properties p);
String getDatabaseId(DataSource dataSource) throws SQLE
10、映射器(mappers)
现在MyBatis的行为是配置上面的配置元素,我们已经准备好来定义映射的SQL语句。但首先,我们需要告诉MyBatis在哪里可以找到它们的。 Java不提供一个很好的手段自动发现,在这方面,所以做到这一点的最好办法是简单地告诉MyBatis在哪里可以找到映射文件。您可以使用相对于类路径的资源引用,或文字,完全合格的URL引用(包括file:/ / /URL)。例如:
&!-- Using classpath relative resources --&
&mapper resource=&org/mybatis/builder/AuthorMapper.xml&/&
&mapper resource=&org/mybatis/builder/BlogMapper.xml&/&
&mapper resource=&org/mybatis/builder/PostMapper.xml&/&
&/mappers&
&!-- Using url fully qualified paths --&
&mapper url=&file:///var/mappers/AuthorMapper.xml&/&
&mapper url=&file:///var/mappers/BlogMapper.xml&/&
&mapper url=&file:///var/mappers/PostMapper.xml&/&
&/mappers&
&!-- Using mapper interface classes --&
&mapper class=&org.mybatis.builder.AuthorMapper&/&
&mapper class=&org.mybatis.builder.BlogMapper&/&
&mapper class=&org.mybatis.builder.PostMapper&/&
&/mappers&
&!-- Register all interfaces in a package as mappers --&
&package name=&org.mybatis.builder&/&
&/mappers&
这些语句简单告诉MyBatis去哪里从这里开始(多种配置方式)。其余的细节都在每个SQL映射文件,这正是下一节将讨论什么。
排名:第1714名
(35)(39)(10)(31)(18)(7)(4)(5)(33)(7)(14)(5)(5)(16)(21)(2)(16)(5)(13)(2)(5)(2)(2)(2)(6)(15)(3)(9)(4)(5)(3)(4)(2)(3)(2)(9)(6)(2)(6)(3)(4)(2)(8)(4)(2)(7)(2)(0)(1)(1)(18)(10)(11)(19)(2)(7)(2)(3)(1)(1)(1)
(107521)

我要回帖

更多关于 codeforces505b 的文章

 

随机推荐