一、整合SpringJDBC

1  JDBC

  JDBC(Java Data Base Connectivity,Java 数据库连接)是一种用于执行 SQL 语句的 Java API,可以为多种关系数据库提供统一访问,它由一组用 Java 语言编写的类和接口组成。JDBC 提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

  1.1 优点

    JDBC 就是一套 Java 访问数据库的 API 规范,利用这套规范屏蔽了各种数据库 API 调用的差异性;

    当 Java 程序需要访问数据库时,直接调用 JDBC API 相关代码进行操作,JDBC 调用各类数据库的驱动包进行交互,最后数据库驱动包和对应的数据库通讯,完成 Java 程序操作数据库。

  1.2 缺点

    直接在 Java 程序中使用 JDBC 比较复杂,需要 7 步才能完成数据库的操作:

      加载数据库驱动 -> 建立数据库连接 -> 创建数据库操作对象 -> 编写SQL语句 -> 利用数据库操作对象执行数据库操作 -> 获取并操作结果集 -> 关闭连接对象和操作对象,回收资源

    利用JDBC操作数据库参考博文

  1.3 新技术

    由于JDBC操作数据库非常复杂,所以牛人们编写了很多ORM框架,其中Hibernate、Mybatis、SpringJDBC最流行;

    时间又过了N年,

    又有牛人在Hibernate的基础上开发了SpringDataJPA,在Mybatis的基础上开发出了MybatisPlus。

2 SpringBoot集成SpringJDBC环境搭建

  2.1 创建一个SpringBoot项目

    引入 spring-boot-starter-web 、spring-boot-starter-jdbc、mysql-connector-java 者三个主要依赖;

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency>

必要依赖

    再引入 devtools、lombok这两个辅助依赖。

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>

辅助依赖

    2.1.1 依赖说明   

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.xunyji</groupId><artifactId>spring_jdbc</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><name>spring_jdbc</name><description>Demo project for Spring Boot</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.0.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

pom.xml

      查看pom.xml依赖图可以知道 spring-boot-starter-jdbc 依赖了spring-jdbc、HikariCP;

      spring-jdbc主要提供JDBC操作相关的接口,HikariCP就是传说中最快的连接池。

  2.2 数据库准备

    2.2.1 创建数据表

DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',`name` varchar(32) DEFAULT NULL COMMENT '用户名',`password` varchar(32) DEFAULT NULL COMMENT '密码',`age`  int DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

users

    2.2.1 配置数据源信息

      坑:Spring Boot 2.1.0 中,com.mysql.jdbc.Driver 已经过期,推荐使用 com.mysql.cj.jdbc.Driver。

      技巧:IDEA是可以连接数据库的哟,而且还可以反向生成对应的实体类哟;IDEA连接数据库并生成实体列参考博文

spring.datasource.url=jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=****
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

    2.2.3 创建实体类

      版案例使用了lombok进行简化编写

package com.xunyji.spring_jdbc.model.entity;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author 王杨帅* @create 2018-11-18 10:43* @desc**/
@Data // 自动生成get、set、toString、equals、hashCode、canEaual方法 和 显示无参构造器
@Builder // 生成builder方法
@NoArgsConstructor // 生成无参构造器
@AllArgsConstructor // 自动生成所有字段的有参构造器,会覆盖无参构造器
public class User {private Long id;private String name;private Integer age;private String email;
}

User.java

  2.3 编写持久层

    2.3.1 持久层接口

package com.xunyji.spring_jdbc.repository;import com.xunyji.spring_jdbc.model.entity.User;
import org.springframework.stereotype.Repository;import java.util.List;/*** @author 王杨帅* @create 2018-11-18 10:53* @desc user表对应的持久层接口**/
public interface UserRepository {Integer save(User user);Integer update(User user);Integer delete(Integer id);List<User> findAll();User findById(Integer id);
}

UserRepository.java

    2.3.2 持久层实现类

      技巧:在实现类中依赖注入 JdbcTemplate,它是Spring提供的用于JDBC操作的工具类。

    @Autowiredprivate JdbcTemplate jdbcTemplate;

package com.xunyji.spring_jdbc.repository.impl;import com.xunyji.spring_jdbc.comm.exception.ExceptionEnum;
import com.xunyji.spring_jdbc.comm.exception.FuryException;
import com.xunyji.spring_jdbc.model.entity.User;
import com.xunyji.spring_jdbc.repository.UserRepository;
import com.xunyji.spring_jdbc.repository.impl.resultmap.UserRowMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import java.util.List;/*** @author 王杨帅* @create 2018-11-18 10:55* @desc user表对应的持久层实现类**/
@Repository
public class UserRepositoryImpl implements UserRepository {private Logger log = LoggerFactory.getLogger(this.getClass());@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic Boolean save(User user) {Integer saveResult = jdbcTemplate.update("INSERT user (name, age, email) VALUES (?, ?, ?)",user.getName(), user.getAge(), user.getEmail());if (saveResult.equals(1)) {return true;}return false;}@Overridepublic Boolean update(User user) throws Exception {Integer updateResult = jdbcTemplate.update("UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?",user.getName(), user.getAge(), user.getEmail(), user.getId());if (updateResult.equals(1)) {return true;}return false;}@Overridepublic Boolean delete(Long id) {Integer deleteResult = jdbcTemplate.update("DELETE FROM user WHERE id = ?",id);if (deleteResult.equals(1)) {return true;}return false;}@Overridepublic List<User> findAll() {return jdbcTemplate.query("SELECT * FROM user",new UserRowMapper());}@Overridepublic User findById(Long id) {try {User user = jdbcTemplate.queryForObject("SELECT id, name, age, email FROM user WHERE id = ?",new Object[]{id},new BeanPropertyRowMapper<>(User.class));return user;} catch (EmptyResultDataAccessException e) {log.info(e.getMessage());throw new FuryException(ExceptionEnum.RESULT_IS_EMPTY);}}
}

UserRepositoryImpl .java

      技巧:新增、更新、删除都是利用JdbcTemplate的update方法,update方法的返回值是执行成功的记录数(需在实现类中根据结果判断是否操作成功);

      技巧:利用JdbcTemplate的queryForObject方法查询单条记录,利用JdbcTemplate的query查询多条记录;

      技巧:利用JdbcTemplate的queryForObject方法查询单条记录时如果查询不到就会抛出EmptyResultDataAccessException,需要进行捕获。

      技巧:查询记录时需要对结果集进行封装,可以直接利用BeanPropertyRowMapper实例进行封装,或者自定义一个实现了UserRowMapper的实现类

    2.3.3 持久层测试类

package com.xunyji.spring_jdbc.repository.impl;import com.xunyji.spring_jdbc.model.entity.User;
import com.xunyji.spring_jdbc.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.util.List;import static org.junit.Assert.*;@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserRepositoryImplTest {@Autowiredprivate UserRepository userRepository;private User user;@Beforepublic void init() {user = User.builder().id(81L).age(33).name("warrior").email("cqdzwys@163.com").build();}@Testpublic void save() throws Exception {Boolean saveNumber = userRepository.save(user);log.info("新增结果为:" + saveNumber);}@Testpublic void update() throws Exception {Boolean updateResult = userRepository.update(user);log.info("更新结果为:" + updateResult);}@Testpublic void delete() throws Exception {Boolean delete = userRepository.delete(81L);log.info("删除结果为:" + delete);}@Testpublic void findById() throws Exception {User byId = userRepository.findById(8L);log.info("获取的数据信息为:" + byId);}@Testpublic void findAll() throws Exception {List<User> all = userRepository.findAll();log.info("获取到的列表数据为:" + all);all.stream().forEach(System.out::println);}}

UserRepositoryImplTest.java

  2.4 编写服务层

    待更新...

  2.5 编写控制层

    待更新...

3 多数据源

  3.1 硬编码实现【不推荐】

    思路:配置多个数据源 -> 通过配置读取两个数据源 -> 利用读取到数据源分别创建各自的JdbcTemplate对应的Bean并交给Spring容器管理 -> 在调用持久层中的方法时动态传入JdbcTemplate实例

    3.1.1 配置多个数据源

      技巧:SpringBoot2.x 默认的数据库驱动为  com.mysql.cj.jdbc.Driver,默认的数据库连接池为 HikariCP

      技巧:HikariCP 连接池读取数据源url时是通过 jdbc-url 获取的

spring:datasource:primary:jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Driversecondary:jdbc-url: jdbc:mysql://localhost:3306/testdemo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Driver

application.yml

    3.1.2 读取配置的数据源

      技巧:ConfigurationProperties用于方法上时会自动读取配置文件中的值并设置到该方法的返回对象上

      技巧:@Primary 注解的作用是当依赖注入有多个类型相同的Bean时,添加了@Primary的那个Bean会默认被注入

    3.1.3 创建JdbcTemplate

    3.1.4 配置类代码汇总

package com.xunyji.spring_jdbc_multi_datasource01.comm.datasource;import com.xunyji.spring_jdbc_multi_datasource01.model.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;import javax.sql.DataSource;/*** @author 王杨帅* @create 2018-11-18 16:17* @desc 多数据源配置**/
@Configuration
@Slf4j
public class DataSourceConfig {@Bean@Primary@ConfigurationProperties(prefix = "spring.datasource.primary")public DataSource primaryDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties(prefix = "spring.datasource.secondary")public DataSource secondaryDataSource() {return DataSourceBuilder.create().build();}@Beanpublic JdbcTemplate primaryJdbcTemplate(@Qualifier(value = "primaryDataSource") DataSource dataSource) {return new JdbcTemplate(dataSource);}@Beanpublic JdbcTemplate secondaryJdbcTemplate(@Qualifier(value = "secondaryDataSource") DataSource dataSource) {return new JdbcTemplate(dataSource);}}

DataSourceConfig.java

    3.1.5 缺点

      这种硬编码方式实现多数据源时,需要在调用持久层方法时指定动态的JdbcTemplate

  3.2 AOP实现【推荐】

    思路:配置多个数据源 -> 读取数据源信息 -> 利用AbstractRoutingDataSource抽象类配置数据源信息 -> 利用AOP实现动态数据源切换

    3.2.1 AOP知识点

      参考博文

    3.2.2 配置多个数据源

spring:datasource:primary:jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Driversecondary:jdbc-url: jdbc:mysql://localhost:3306/testdemo2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Driver

application.yml

    3.2.3 创建AbstractRoutingDataSource实现类

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.lang.Nullable;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author 王杨帅* @create 2018-11-18 21:18* @desc 动态数据源**/
public class DynamicDataSource extends AbstractRoutingDataSource {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public DynamicDataSource(DataSource defaultTargetDataSource,Map<String, DataSource> targetDataSources) {// 01 通过父类设置默认数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);// 02 通过父类设置数据源集合super.setTargetDataSources(new HashMap<>(targetDataSources));// 03 通过父类对数据源进行解析 https://blog.csdn.net/u011463444/article/details/72842500
        super.afterPropertiesSet();}@Nullable@Overrideprotected Object determineCurrentLookupKey() {// 获取数据源,如果没有指定,则为默认数据源return getDataSource();}/*** 设置数据源* @param dataSource*/public static void setDataSource(String dataSource) {contextHolder.set(dataSource);}/*** 获取数据源* @return*/public static String getDataSource() {return contextHolder.get();}/*** 清除数据源*/public static void clearDataSource() {contextHolder.remove();}
}

DynamicDataSource.java

    3.2.4 读取数据源并配置动态数据源

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import javax.xml.crypto.Data;
import java.util.HashMap;
import java.util.Map;/*** @author 王杨帅* @create 2018-11-18 21:42* @desc 动态数据源读取与配置**/
@Configuration
public class DynamicDataSourceConfig {/*** 读取并配置数据源1* @return*/@Bean@ConfigurationProperties(prefix = "spring.datasource.primary")public DataSource primaryDataSource() {return DataSourceBuilder.create().build();}/*** 读取并配置数据源2* @return*/@Bean@ConfigurationProperties(prefix = "spring.datasource.secondary")public DataSource secondaryDataSource() {return DataSourceBuilder.create().build();}/*** 根据数据源1和数据源2配置动态数据源* @param primaryDataSource* @param secondaryDataSource* @return*/@Bean@Primarypublic DynamicDataSource dataSource(DataSource primaryDataSource,DataSource secondaryDataSource) {Map<String, DataSource> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceNames.FIRST, primaryDataSource);targetDataSources.put(DataSourceNames.SECOND, secondaryDataSource);return new DynamicDataSource(primaryDataSource, targetDataSources);}}

DynamicDataSourceConfig.java

    3.2.5 创建指定数据源的注解

package com.xunyji.spring_jdbc_datasource.comm.datasource;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String name() default "";
}

DataSource.java

    3.2.6 创建常量接口

package com.xunyji.spring_jdbc_datasource.comm.datasource;/*** @author 王杨帅* @create 2018-12-19 22:28* @desc**/
public interface DataSourceNames {String FIRST = "first";String SECOND = "second";
}

DataSourceNames.java

    3.2.7 创建切换数据源的AOP

package com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** @author 王杨帅* @create 2018-11-18 21:53* @desc 数据源切换AOP**/
@Aspect
@Component
@Slf4j
public class DataSourceAspect implements Ordered{@Pointcut("@annotation(com.xunyji.spring_jdbc_multi_datasource02.comm.datasouce.DataSource)")public void dataSourcePointCut() {}@Around("dataSourcePointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();DataSource ds = method.getAnnotation(DataSource.class);if(ds == null){DynamicDataSource.setDataSource(DataSourceNames.FIRST);log.debug("set datasource is " + DataSourceNames.FIRST);}else {DynamicDataSource.setDataSource(ds.name());log.debug("set datasource is " + ds.name());}try {return point.proceed();} finally {DynamicDataSource.clearDataSource();log.debug("clean datasource");}}@Overridepublic int getOrder() {return 1;}
}

DataSourceAspect.java

    3.2.8 常见错误

      错区1:由于利用AOP实现的所数据源,所以需要额外导入Aspect相关的依赖

        <dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></dependency>

      错误2:启动应用后会出现循环应用错误,错误信息如下

      原因:SpringBoot默认对数据源进行了配置,如果想要动态数据源生效就必须关闭数据源的自动配置

      解决:在启动类的注解中排除掉数据源自动配置类即可

    3.2.9 知识点复习

      》ThreadLocal原理与应用场景

      》AbstractRoutingDataSource原理解析

      

二、整合MyBatis

1 ORM框架

(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)

  1.1 概念

    对象关系映射(Object Relational Mapping,ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

  1.2 为什么需要ORM

    当你开发一个应用程序的时候(不使用 O/R Mapping),可能会写不少数据访问层代码,用来从数据库保存、删除、读取对象信息等;在 DAL 中写了很多的方法来读取对象数据、改变状态对象等任务,而这些代码写起来总是重复的。针对这些问题 ORM 提供了解决方案,简化了将程序中的对象持久化到关系数据库中的操作。

    ORM 框架的本质是简化编程中操作数据库的编码,在 Java 领域发展到现在基本上就剩两家最为流行,一个是宣称可以不用写一句 SQL 的 Hibernate,一个是以动态 SQL 见长的 MyBatis,两者各有特点。在企业级系统开发中可以根据需求灵活使用,会发现一个有趣的现象:传统企业大都喜欢使用 Hibernate,而互联网行业通常使用 MyBatis。

  

2 MyBatis介绍

(摘自:https://gitbook.cn/gitchat/column/5b86228ce15aa17d68b5b55a/topic/5be8f0552c33167c317c6a7f)

  2.1 概念

    MyBatis 支持普通的 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索封装。MaBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录。

  2.2 优点

    SQL 被统一提取出来,便于统一管理和优化
    SQL 和代码解耦,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰、更易维护、更易单元测试
    提供映射标签,支持对象与数据库的 ORM 字段关系映射
    提供对象关系映射标签,支持对象关系组件维护
    灵活书写动态 SQL,支持各种条件来动态生成不同的 SQL

  2.3 缺点   

    编写 SQL 语句时工作量很大,尤其是字段多、关联表多时,更是如此
    SQL 语句依赖于数据库,导致数据库移植性差

3 mybatis原理解析

  待更新2018年12月27日10:54:46

4 SpringBoot整合Mybatis(xml版本)

  4.1 引入依赖

    引入web启动依赖和mysql、mybatis相关依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency>

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.1.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.xunyji</groupId><artifactId>mybatis_xml_datasource</artifactId><version>0.0.1-SNAPSHOT</version><name>mybatis_xml_datasource</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

pom.xml

  4.2 规范项目路径

    4.2.1 路径说明

      mapper接口:就是一般的持久层接口而已,一般放在repository包下;mapper接口位于程序源文件目录

      mapper文件:就是书写sql语句的文件,每个mapper映射文件都和一个mapper接口一一对应;mapper映射文件和mybatis的配置文件都位于resources目录

      mybatis配置文件:就是mybatis的一些基本配置信息;mybatis配置文件一般和存放mappr映射文件的目录处于一个目录下【本案例处于resouces目录下的mybatis目录下】

      路径配置:由于mybatis官方提供的启动包提供了一个配置类来配置mapper映射文件和配置文件的路径【org.mybatis.spring.boot.autoconfigure.MybatisProperties】

      项目结构如下所示:

    4.2.2 配置类

      配置类:org.mybatis.spring.boot.autoconfigure.MybatisProperties,可配置的选项和配置实例如下所示

  4.3 数据源配置

    在application.yml文件中配置基本数据源

spring:datasource:url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Driver

  4.4 mybatis配置文件

    mybatis配置文件位于resouces目录下的mybatis文件夹下,需要在application.yml中指定mybatis配置文件的位置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><typeAliases><typeAlias alias="Integer" type="java.lang.Integer" /><typeAlias alias="Long" type="java.lang.Long" /><typeAlias alias="HashMap" type="java.util.HashMap" /><typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" /><typeAlias alias="ArrayList" type="java.util.ArrayList" /><typeAlias alias="LinkedList" type="java.util.LinkedList" /></typeAliases>
</configuration>

  4.5 数据表和实体类

    在数据源对应的数据库中创建一张users表

/*
Navicat MySQL Data TransferSource Server         : mysql5.4
Source Server Version : 50540
Source Host           : localhost:3306
Source Database       : testdemoTarget Server Type    : MYSQL
Target Server Version : 50540
File Encoding         : 65001Date: 2018-12-25 15:16:15
*/SET FOREIGN_KEY_CHECKS=0;-- ----------------------------
-- Table structure for `users`
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',`userName` varchar(32) DEFAULT NULL COMMENT '用户名',`passWord` varchar(32) DEFAULT NULL COMMENT '密码',`user_sex` varchar(32) DEFAULT NULL,`nick_name` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=39 DEFAULT CHARSET=utf8;-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES ('1', 'assassin', '阿斯蒂芬', 'MAN', '暗室逢灯');
INSERT INTO `users` VALUES ('28', 'aa', 'a123456', 'MAN', null);
INSERT INTO `users` VALUES ('29', 'bb', 'b123456', 'WOMAN', null);
INSERT INTO `users` VALUES ('30', 'cc', '0134123', 'WOMAN', null);
INSERT INTO `users` VALUES ('31', 'aa', 'a123456', 'MAN', null);
INSERT INTO `users` VALUES ('32', 'bb', 'b123456', 'WOMAN', null);
INSERT INTO `users` VALUES ('33', 'cc', 'b123456', 'WOMAN', null);
INSERT INTO `users` VALUES ('34', null, '123321', null, null);
INSERT INTO `users` VALUES ('35', null, '123321', 'MAN', null);
INSERT INTO `users` VALUES ('36', null, '123321', 'MAN', null);
INSERT INTO `users` VALUES ('37', '王杨帅', '1234', null, null);
INSERT INTO `users` VALUES ('38', '杨玉林', null, 'MAN', null);

users

    在项目中创建一个实体类类User和数据源中的users表对应

    技巧01:可以利用IDEA直接生成,参考文档

    注意:User实体类中用到了一个枚举

package com.xunyji.mybatis_xml.model.enums;/*** @author 王杨帅* @create 2018-12-25 14:17* @desc**/
public enum UserSexEnum {WOMAN,MAN
}

UserSexEnum.java

package com.xunyji.mybatis_xml.model.entity;import com.xunyji.mybatis_xml.model.enums.UserSexEnum;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;/*** @author 王杨帅* @create 2018-12-25 14:18* @desc**/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {private Long id;private String userName;private String passWord;private UserSexEnum userSex;private String nickName;
}

User.java

  4.6 创建mapper接口

    技巧01:跟一般的接口一样,需要在接口上标注@Repository注解

package com.xunyji.mybatis_xml.repository;import com.xunyji.mybatis_xml.model.entity.User;
import com.xunyji.mybatis_xml.model.param.UserParam;
import org.springframework.stereotype.Repository;import java.util.List;/*** @author 王杨帅* @create 2018-12-23 11:31* @desc**/
@Repository
public interface UserRepository {List<User> getAll();User getOne(Long id);void insert(User user);void update(User user);void delete(Long id);List<User> getList(UserParam userParam);Integer getCount(UserParam userParam);
}

UserRepository.java

  4.7 创建mapper映射文件

    技巧01:mapper映射文件位于resources目录下,而且需要在application.yml中配置mapper映射文件的位置

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace="com.xunyji.mybatis_xml.repository.UserRepository" ><!--表结构和实体类的映射关系 start--><resultMap id="BaseResultMap" type="com.xunyji.mybatis_xml.model.entity.User" ><id column="id" property="id" jdbcType="BIGINT" /><result column="userName" property="userName" jdbcType="VARCHAR" /><result column="passWord" property="passWord" jdbcType="VARCHAR" /><result column="user_sex" property="userSex" jdbcType="VARCHAR" javaType="com.xunyji.mybatis_xml.model.enums.UserSexEnum"/><result column="nick_name" property="nickName" jdbcType="VARCHAR" /></resultMap><!--表结构和实体类的映射关系 end--><sql id="Base_Column_List" >id, userName, passWord, user_sex, nick_name</sql><sql id="Base_Where_List"><if test="userName != null  and userName != ''">and userName = #{userName}</if><if test="userSex != null and userSex != ''">and user_sex = #{userSex}</if></sql><!--查询所有数据 start--><select id="getAll" resultMap="BaseResultMap"  >SELECT<include refid="Base_Column_List" />FROM users</select><!--查询所有数据 end--><!--分页查询 start--><select id="getList" resultMap="BaseResultMap" parameterType="com.xunyji.mybatis_xml.model.param.UserParam">select<include refid="Base_Column_List" />from userswhere 1=1<include refid="Base_Where_List" />order by id ASClimit #{beginLine} , #{pageSize}</select><!--分页查询 end--><!--记录总数 start--><select id="getCount" resultType="Integer" parameterType="com.xunyji.mybatis_xml.model.param.UserParam">selectcount(1)from userswhere 1 = 1<include refid="Base_Where_List" /></select><!--记录总数--><!--根据ID获取数据 start--><select id="getOne" parameterType="Long" resultMap="BaseResultMap" >SELECT<include refid="Base_Column_List" />FROM usersWHERE id = #{id}</select><!--根据ID获取数据 end--><!--插入数据 start--><insert id="insert" parameterType="com.xunyji.mybatis_xml.model.entity.User" >INSERT INTOusers(userName,passWord,user_sex, nick_name)VALUES(#{userName}, #{passWord}, #{userSex}, #{nickName})</insert><!--插入数据 end--><!--更新数据 start--><update id="update" parameterType="com.xunyji.mybatis_xml.model.entity.User" >UPDATEusersSET<if test="userName != null">userName = #{userName},</if><if test="passWord != null">passWord = #{passWord},</if>nick_name = #{nickName}WHEREid = #{id}</update><!--更新数据 end--><!--删除数据 start--><delete id="delete" parameterType="Long" >DELETE FROMusersWHEREid =#{id}</delete><!--删除数据 end--></mapper>

UserMapper.xml

  4.8 必备配置

    4.8.1 启动类配置

      需要在启动类上指定mapper接口路径

    4.8.2 application.yml配置

spring:datasource:url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Drivermybatis:config-location: classpath:mybatis/mybatis-config.xmlmapper-locations: classpath:mybatis/mapper/*.xmltype-aliases-package: com.xunyji.mybatis_xml.model

application.yml

  4.9 测试类

package com.xunyji.mybatis_xml.repository;import com.sun.org.apache.xml.internal.security.keys.keyresolver.implementations.X509IssuerSerialResolver;
import com.xunyji.mybatis_xml.model.entity.User;
import com.xunyji.mybatis_xml.model.enums.UserSexEnum;
import com.xunyji.mybatis_xml.model.param.UserParam;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;import static org.junit.Assert.*;@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserRepositoryTest {@Autowiredprivate UserRepository userRepository;@Testpublic void getAll() throws Exception {List<User> userList = userRepository.getAll();userList.forEach(System.out::println);}@Testpublic void getOne() throws Exception {List<User> userList = userRepository.getAll();log.info("获取到的user列表为:" + userList);List<Long> idList = userList.stream().map(user -> user.getId()).collect(Collectors.toList());log.info("获取到的ID列表为:" + idList);int index = new Random().nextInt(idList.size()); // 随机获取一个idList的索引号Long indexValue = idList.get(index); // 获取随机索引号index在idList中的对应值
User user = userRepository.getOne(indexValue);log.info("ID值为{}的用户信息为:{}", indexValue, user);}@Testpublic void insert() throws Exception {User user = User.builder().userName("王毅凌").passWord("100101").userSex(UserSexEnum.MAN).nickName("asdasdf").build();log.info("封装的user对象为:" + user);userRepository.insert(user);List<User> all = userRepository.getAll();all.forEach(System.out::println);}@Testpublic void update() throws Exception {User user = User.builder().id(38l).userName("王毅凌").passWord("010101").userSex(UserSexEnum.MAN).nickName("毅凌").build();userRepository.update(user);List<User> all = userRepository.getAll();all.forEach(System.out::println);}@Testpublic void delete() throws Exception {log.info("删除前:");userRepository.getAll().forEach(System.out::println);userRepository.delete(41l);log.info("删除后:");userRepository.getAll().forEach(System.out::println);}@Testpublic void getList() throws Exception {UserParam userParam = UserParam.builder().build();userParam.setCurrentPage(2);userParam.setPageSize(3);log.info("起始页码为:" + userParam.getBeginLine());List<User> list = userRepository.getList(userParam);list.forEach(System.out::println);}@Testpublic void getCount() throws Exception {UserParam userParam = UserParam.builder().build();userParam.setCurrentPage(2);userParam.setPageSize(3);Integer count = userRepository.getCount(userParam);log.info("记录数为:" + count);}@Testpublic void testDemo() {}}

View Code

  4.10 多数据源

    4.10.1 多数据源配置文件

spring:datasource:primary:jdbc-url: jdbc:mysql://localhost:3306/testdemo?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Driversecondary:jdbc-url: jdbc:mysql://localhost:3306/testdemo4?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: rootpassword: 182838driver-class-name: com.mysql.cj.jdbc.Driver

    4.10.2 数据源名称

      技巧01:利用一个接口来管理数据源名称,也可以利用一个枚举类型来实现

package com.xunyji.mybatis_xml_datasource.config.datasource;/*** @author 王杨帅* @create 2018-12-27 15:22* @desc 多数据源名称**/
public interface DataSourceNames {String FIRST = "first";String SECOND = "second";
}

DataSourceNames.java

    4.10.3 扩展Spring的AbstractRoutingDataSource抽象类

      AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现多数据 源的核心,并对该方法进行Override。

package com.xunyji.mybatis_xml_datasource.config.datasource;import com.sun.xml.internal.bind.v2.util.DataSourceSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author 王杨帅* @create 2018-12-27 15:23* @desc 读取配置文件中的数据源信息**/
@Configuration
public class DynamicDataSourceConfig {@Bean@ConfigurationProperties(value = "spring.datasource.primary")public DataSource firstDataSource() {return DataSourceBuilder.create().build();}@Bean@ConfigurationProperties(value = "spring.datasource.secondary")public DataSource secondDataSource() {return DataSourceBuilder.create().build();}@Bean@Primarypublic DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {Map<String, DataSource> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceNames.FIRST, firstDataSource);targetDataSources.put(DataSourceNames.SECOND, secondDataSource);return new DynamicDataSource(firstDataSource, targetDataSources);}}

DynamicDataSourceConfig.java

    4.10.4 配置DataSource,指定数据源的信息

package com.xunyji.mybatis_xml_datasource.config.datasource;import com.sun.xml.internal.bind.v2.util.DataSourceSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author 王杨帅* @create 2018-12-27 15:23* @desc 读取配置文件中的数据源信息**/
@Configuration
public class DynamicDataSourceConfig {/*** 读取第一个数据源信息* @return*/@Bean@ConfigurationProperties(value = "spring.datasource.primary")public DataSource firstDataSource() {return DataSourceBuilder.create().build();}/*** 读取第二个数据源信息* @return*/@Bean@ConfigurationProperties(value = "spring.datasource.secondary")public DataSource secondDataSource() {return DataSourceBuilder.create().build();}/*** 配置主数据源,并将数据源信息集合传入DynamicDataSource中* @param firstDataSource* @param secondDataSource* @return*/@Bean@Primarypublic DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {Map<String, DataSource> targetDataSources = new HashMap<>();targetDataSources.put(DataSourceNames.FIRST, firstDataSource);targetDataSources.put(DataSourceNames.SECOND, secondDataSource);return new DynamicDataSource(firstDataSource, targetDataSources);}}

DynamicDataSourceConfig.java

    4.10.5 数据源切换注解

package com.xunyji.mybatis_xml_datasource.config.datasource;import java.lang.annotation.*;/*** 数据源切换注解*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {String name() default "";
}

DataSource.java

    4.10.6 用于切换数据源的切面

package com.xunyji.mybatis_xml_datasource.config.datasource;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** @author 王杨帅* @create 2018-12-27 15:58* @desc 切面类**/
@Aspect
@Component
@Slf4j
public class DataSourceAspect implements Ordered {@Pointcut("@annotation(com.xunyji.mybatis_xml_datasource.config.datasource.DataSource)")public void dataSourcePointCut() {}@Around("dataSourcePointCut()")public Object around(ProceedingJoinPoint point) throws Throwable {MethodSignature signature = (MethodSignature) point.getSignature();Method method = signature.getMethod();DataSource ds = method.getAnnotation(DataSource.class);if(ds == null){DynamicDataSource.setDataSource(DataSourceNames.FIRST);log.debug("set datasource is " + DataSourceNames.FIRST);}else {DynamicDataSource.setDataSource(ds.name());log.debug("set datasource is " + ds.name());}try {return point.proceed();} finally {DynamicDataSource.clearDataSource();log.debug("clean datasource");}}@Overridepublic int getOrder() {return 1;}
}

DataSourceAspect.java

    4.10.7 启动类配置

      由于SpringBoot的自动配置原理,所以需要将SpringBoot配置的DataSource排除掉

    4.10.8 添加AOP依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>

    4.10.9 利用注解切换数据源

      在Repository层中的方法中利用自定义的数据源切换注解标注该方法使用的数据源

转载于:https://www.cnblogs.com/NeverCtrl-C/p/9978406.html

SpringBoot31 整合SpringJDBC、整合MyBatis、利用AOP实现多数据源相关推荐

  1. Spring AOP之四:利用AOP实现动态数据源切换

    2019独角兽企业重金招聘Python工程师标准>>> 简介和依赖 项目的前提是安装了MySQL数据库,并且建立了2个数据库一个是master,一个是slave,并且这2个数据库都有 ...

  2. spring boot mybatis 整合_Spring、MyBatis和SpringMVC的整合

    SSM框架整合的知识. 不用maven,为什么呢?主要是帮助更好的理解有哪些包,这样更加透彻.当然了,使用maven会更方便一点. 1 jar包管理 2 整合思路 spring在进行管理时,是很有条理 ...

  3. SpringBoot实战:整合Redis、mybatis,封装RedisUtils工具类等(附源码)

    创建SpringBoot项目 在线创建方式 网址:https://start.spring.io/ 然后创建Controller.Mapper.Service包 SpringBoot整合Redis 引 ...

  4. 使用IDEA整合SpringMVC和Mybatis(SSM框架)(二)

    上一篇已经搭建了一个基础的springMVC项目,现在加入mybatis的相关配置. 写代码之前先在数据库中建一张表,如下sql: DROP TABLE IF EXISTS `user_info`; ...

  5. SpringBoot整合:Druid、MyBatis、MyBatis-Plus、多数据源、knife4j、日志、Redis,Redis的Java操作工具类、封装发送电子邮件等等

    SpringBoot笔记 一.SpringBoot 介绍 1.1.SpringBoot简介 SpringBoot 是一个快速开发的框架, 封装了Maven常用依赖.能够快速的整合第三方框架:简化XML ...

  6. Spring Boot实战:整合Redis、MyBatis,封装RedisUtils工具类

    创建 Spring Boot 项目 在线创建方式 网址:https://start.spring.io/ 然后创建Controller.Mapper.Service包 SpringBoot整合Redi ...

  7. 一起来学大数据|整合SpringMVC与Mybatis,各司其职相得益彰

    我们在上篇的文章中学习了在网页上展示一个简单 的商品页面,而我们真正需要的是与数据库之间结合. 今天我们就一起来看看持久层最优秀的mybatis和控制层最优秀的spring最佳整合. 整合思路 Dao ...

  8. SSM框架整合—详细整合教程(Spring+SpringMVC+MyBatis)

    SSM框架整合-详细整合教程(Spring+SpringMVC+MyBatis) ✨博主介绍 MyBatis和Spring整合 1.整合原因 2.整合条件 3.整合入门 4.整合MyBatis现有配置 ...

  9. activiti+5.21+mysql_ydl-workflow基于SAAS服务,完美整合springboot + activiti5 + MyBatis

    workflow V1.0 ydl-workflow基于SAAS服务,完美整合springboot + activiti5 + MyBatis 通用 Mapper + 分页插件 PageInfo! 说 ...

  10. SpringBoot实战:整合Redis、mybatis,封装RedisUtils工具类等

    创建SpringBoot项目 在线创建方式 网址:https://start.spring.io/ 然后创建Controller.Mapper.Service包 SpringBoot整合Redis 引 ...

最新文章

  1. AI一分钟 | 小米智能音箱mini版曝光,或售199元;特朗普被指利用AI竞选成功
  2. Git credential helper 让你的 https不再需要输入用户名密码
  3. 一篇文章带你飞,轻松弄懂 CDN 技术原理
  4. mysql pt_mysql之pt工具之pt-fifo-split用法介绍
  5. python——学习笔记1
  6. ABP入门系列(14)——应用BootstrapTable表格插件
  7. 聚水潭是如何基于AnalyticDB for PostgreSQL 构筑海量实时数仓平台的
  8. 阿里与网易考拉收购案谈崩?后者股价下跌5.01%
  9. IDEA 控制台显示Run Dashboard
  10. floodlight路由机制分析
  11. 【入门书籍】新手入门机器学习,强烈推荐这几本书籍
  12. OMS智能订单管理系统
  13. retainall java_瞬间教你学会使用java中list的retainAll方法
  14. Label mx条码软件导入Excel处理异常解决方法
  15. java安装 2203_win7系统无法安装java程序提示“内部错误2203”的解决方法
  16. 苹果Swift语言入门教程
  17. 差异化地推手段,让营销效果翻十倍!
  18. javax.crypto.Cipher类--加密和解密
  19. AIO,BIO,NIO详解
  20. 坐牢后,我被安排去写代码...

热门文章

  1. 山东理工大学acm非专业程序设计基础答案
  2. Java:pdf文件中添加图片
  3. 免费注册的域名.tk
  4. Android 自定义实现倒三角图片
  5. css实现--三角形/箭头(上下左右)--详细原理
  6. OptiFDTD应用:纳米盘型谐振腔等离子体波导滤波器
  7. 至强服务器虚拟机黑苹果,[经验] AMD/Intel CPU VMware虚拟机安装黑苹果
  8. Allegro 常见问题
  9. 统计模型评价准则 AIC
  10. 除了UL认证,开拓美国市场必备认证有哪些?