Spring Boot2 系列教程(二十三)理解 Spring Data Jpa

有很多讀者留言希望松哥能好好聊聊 Spring Data Jpa! 其實這個話題松哥以前零零散散的介紹過,在我的書里也有介紹過,但是在公眾號中還沒和大夥聊過,因此本文就和大家來仔細聊聊 Spring Data 和 Jpa!

本文大綱:

1. 故事的主角

1.1 Jpa

1.1.1 JPA 是什麼

  1. Java Persistence API:用於對象持久化的 API
  2. Java EE 5.0 平台標準的 ORM 規範,使得應用程序以統一的方式訪問持久層

1.1.2 JPA 和 Hibernate 的關係

  1. JPA 是 Hibernate 的一個抽象(就像 JDBC 和 JDBC 驅動的關係);
  2. JPA 是規範:JPA 本質上就是一種 ORM 規範,不是 ORM 框架,這是因為 JPA 並未提供 ORM 實現,它只是制訂了一些規範,提供了一些編程的 API 接口,但具體實現則由 ORM 廠商提供實現;
  3. Hibernate 是實現:Hibernate 除了作為 ORM 框架之外,它也是一種 JPA 實現
  4. 從功能上來說, JPA 是 Hibernate 功能的一個子集

1.1.3 JPA 的供應商

JPA 的目標之一是制定一個可以由很多供應商實現的 API,Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的實現,Jpa 供應商有很多,常見的有如下四種:

  1. Hibernate:JPA 的始作俑者就是 Hibernate 的作者,Hibernate 從 3.2 開始兼容 JPA。
  2. OpenJPA:OpenJPA 是 Apache 組織提供的開源項目。
  3. TopLink:TopLink 以前需要收費,如今開源了。
  4. EclipseLink

1.1.4 JPA 的優勢

  1. 標準化: 提供相同的 API,這保證了基於 JPA 開發的企業應用能夠經過少量的修改就能夠在不同的 JPA 框架下運行。
  2. 簡單易用,集成方便: JPA 的主要目標之一就是提供更加簡單的編程模型,在 JPA 框架下創建實體和創建 Java 類一樣簡單,只需要使用 javax.persistence.Entity 進行註解;JPA 的框架和接口也都非常簡單。
  3. 可媲美JDBC的查詢能力: JPA的查詢語言是面向對象的,JPA 定義了獨特的JPQL,而且能夠支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能夠提供的高級查詢特性,甚至還能夠支持子查詢。
  4. 支持面向對象的高級特性: JPA 中能夠支持面向對象的高級特性,如類之間的繼承、多態和類之間的複雜關係,最大限度的使用面向對象的模型

1.1.5 JPA 包含的技術

  1. ORM 映射元數據:JPA 支持 XML 和 JDK 5.0 註解兩種元數據的形式,元數據描述對象和表之間的映射關係,框架據此將實體對象持久化到數據庫表中。
  2. JPA 的 API:用來操作實體對象,執行CRUD操作,框架在後台完成所有的事情,開發者從繁瑣的 JDBC 和 SQL 代碼中解脫出來。
  3. 查詢語言(JPQL):這是持久化操作中很重要的一個方面,通過面向對象而非面向數據庫的查詢語言查詢數據,避免程序和具體的 SQL 緊密耦合。

1.2 Spring Data

Spring Data 是 Spring 的一個子項目。用於簡化數據庫訪問,支持NoSQL 和 關係數據存儲。其主要目標是使數據庫的訪問變得方便快捷。Spring Data 具有如下特點:

  • SpringData 項目支持 NoSQL 存儲:
    1. MongoDB (文檔數據庫)
    2. Neo4j(圖形數據庫)
    3. Redis(鍵/值存儲)
    4. Hbase(列族數據庫)
  • SpringData 項目所支持的關係數據存儲技術:
    1. JDBC
    2. JPA
  • Spring Data Jpa 致力於減少數據訪問層 (DAO) 的開發量. 開發者唯一要做的,就是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成
  • 框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個UserDao.findUserById() 這樣一個方法聲明,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 對象。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。

2. 主角的故事

2.1 Jpa 的故事

為了讓大夥徹底把這兩個東西學會,這裏我就先來介紹單純的 Jpa 使用,然後我們再結合 Spring Data 來看 Jpa 如何使用。

整體步驟如下:

  • 1.使用 IntelliJ IDEA 創建項目,創建時選擇 JavaEE Persistence ,如下:
  • 2.創建成功后,添加依賴 jar,由於 Jpa 只是一個規範,因此我們說用 Jpa 實際上必然是用 Jpa 的某一種實現,那麼是哪一種實現呢?當然就是 Hibernate 了,所以添加的 jar,實際上來自 Hibernate,如下:
  • 3.添加實體類

接下來在項目中添加實體類,如下:

@Entity(name = "t_book")
public class Book {
    private Long id;
    private String name;
    private String author;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }
    // 省略其他getter/setter
}

首先 @Entity 註解表示這是一個實體類,那麼在項目啟動時會自動針對該類生成一張表,默認的表名為類名,@Entity 註解的 name 屬性表示自定義生成的表名。@Id 註解表示這個字段是一個 id,@GeneratedValue 註解表示主鍵的自增長策略,對於類中的其他屬性,默認都會根據屬性名在表中生成相應的字段,字段名和屬性名相同,如果開發者想要對字段進行定製,可以使用 @Column 註解,去配置字段的名稱,長度,是否為空等等。

  • 4.創建 persistence.xml 文件

JPA 規範要求在類路徑的 META-INF 目錄下放置 persistence.xml,文件的名稱是固定的

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
    <persistence-unit name="NewPersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <class>org.javaboy.Book</class>
        <properties>
            <property name="hibernate.connection.url"
                      value="jdbc:mysql:///jpa01?useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
            <property name="hibernate.connection.username" value="root"/>
            <property name="hibernate.connection.password" value="123"/>
            <property name="hibernate.archive.autodetection" value="class"/>
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
        </properties>
    </persistence-unit>
</persistence>

注意:

  1. persistence-unit 的 name 屬性用於定義持久化單元的名字, 必填。
  2. transaction-type:指定 JPA 的事務處理策略。RESOURCE_LOCAL:默認值,數據庫級別的事務,只能針對一種數據庫,不支持分佈式事務。如果需要支持分佈式事務,使用JTA:transaction-type=”JTA”
  3. class 節點表示顯式的列出實體類
  4. properties 中的配置分為兩部分:數據庫連接信息以及Hibernate信息
  • 5.執行持久化操作
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("NewPersistenceUnit");
EntityManager manager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
Book book = new Book();
book.setAuthor("羅貫中");
book.setName("三國演義");
manager.persist(book);
transaction.commit();
manager.close();
entityManagerFactory.close();

這裏首先根據配置文件創建出來一個 EntityManagerFactory ,然後再根據 EntityManagerFactory 的實例創建出來一個 EntityManager ,然後再開啟事務,調用 EntityManager 中的 persist 方法執行一次持久化操作,最後提交事務,執行完這些操作后,數據庫中舊多出來一個 t_book 表,並且表中有一條數據。

2.1.1 關於 JPQL

  1. JPQL 語言,即 Java Persistence Query Language 的簡稱。JPQL 是一種和 SQL 非常類似的中間性和對象化查詢語言,它最終會被編譯成針對不同底層數據庫的 SQL 查詢,從而屏蔽不同數據庫的差異。JPQL語言的語句可以是 select 語句、update 語句或 delete 語句,它們都通過 Query 接口封裝執行。
  2. Query接口封裝了執行數據庫查詢的相關方法。調用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以獲得查詢對象,進而可調用 Query 接口的相關方法來執行查詢操作。
  3. Query接口的主要方法如下:
  • int executeUpdate(); | 用於執行update或delete語句。
  • List getResultList(); | 用於執行select語句並返回結果集實體列表。
  • Object getSingleResult(); | 用於執行只返回單個結果實體的select語句。
  • Query setFirstResult(int startPosition); | 用於設置從哪個實體記錄開始返回查詢結果。
  • Query setMaxResults(int maxResult); | 用於設置返回結果實體的最大數。與setFirstResult結合使用可實現分頁查詢。
  • Query setFlushMode(FlushModeType flushMode); | 設置查詢對象的Flush模式。參數可以取2個枚舉值:FlushModeType.AUTO 為自動更新數據庫記錄,FlushMode Type.COMMIT 為直到提交事務時才更新數據庫記錄。
  • setHint(String hintName, Object value); | 設置與查詢對象相關的特定供應商參數或提示信息。參數名及其取值需要參考特定 JPA 實現庫提供商的文檔。如果第二個參數無效將拋出IllegalArgumentException異常。
  • setParameter(int position, Object value); | 為查詢語句的指定位置參數賦值。Position 指定參數序號,value 為賦給參數的值。
  • setParameter(int position, Date d, TemporalType type); | 為查詢語句的指定位置參數賦 Date 值。Position 指定參數序號,value 為賦給參數的值,temporalType 取 TemporalType 的枚舉常量,包括 DATE、TIME 及 TIMESTAMP 三個,,用於將 Java 的 Date 型值臨時轉換為數據庫支持的日期時間類型(java.sql.Date、java.sql.Time及java.sql.Timestamp)。
  • setParameter(int position, Calendar c, TemporalType type); | 為查詢語句的指定位置參數賦 Calenda r值。position 指定參數序號,value 為賦給參數的值,temporalType 的含義及取捨同前。
  • setParameter(String name, Object value); | 為查詢語句的指定名稱參數賦值。
  • setParameter(String name, Date d, TemporalType type); | 為查詢語句的指定名稱參數賦 Date 值,用法同前。
  • setParameter(String name, Calendar c, TemporalType type); | 為查詢語句的指定名稱參數設置Calendar值。name為參數名,其它同前。該方法調用時如果參數位置或參數名不正確,或者所賦的參數值類型不匹配,將拋出 IllegalArgumentException 異常。

2.1.2 JPQL 舉例

和在 SQL 中一樣,JPQL 中的 select 語句用於執行查詢。其語法可表示為:

select_clause form_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause]

其中:

  1. from 子句是查詢語句的必選子句。
  2. select 用來指定查詢返回的結果實體或實體的某些屬性。
  3. from 子句聲明查詢源實體類,並指定標識符變量(相當於SQL表的別名)。
  4. 如果不希望返回重複實體,可使用關鍵字 distinct 修飾。select、from 都是 JPQL 的關鍵字,通常全大寫或全小寫,建議不要大小寫混用。

在 JPQL 中,查詢所有實體的 JPQL 查詢語句很簡單,如下:

select o from Order o

或

select o from Order as o

這裏關鍵字 as 可以省去,標識符變量的命名規範與 Java 標識符相同,且區分大小寫,調用 EntityManager 的 createQuery() 方法可創建查詢對象,接着調用 Query 接口的 getResultList() 方法就可獲得查詢結果集,如下:

Query query = entityManager.createQuery( "select o from Order o"); 
List orders = query.getResultList();
Iterator iterator = orders.iterator();
while(iterator.hasNext() ) {
  // 處理Order
}

其他方法的與此類似,這裏不再贅述。

2.2 Spring Data 的故事

在 Spring Boot 中,Spring Data Jpa 官方封裝了太多東西了,導致很多人用的時候不知道底層到底是怎麼配置的,本文就和大夥來看看在手工的 Spring 環境下,Spring Data Jpa 要怎麼配置,配置完成后,用法和 Spring Boot 中的用法是一致的。

2.2.1 基本環境搭建

首先創建一個普通的 Maven 工程,並添加如下依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-oxm</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.27</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-expression</artifactId>
        <version>5.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.12.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-jpamodelgen</artifactId>
        <version>5.2.12.Final</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.29</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
        <version>1.11.3.RELEASE</version>
    </dependency>
</dependencies>

這裏除了 Jpa 的依賴之外,就是 Spring Data Jpa 的依賴了。

接下來創建一個 User 實體類,創建方式參考 Jpa 中實體類的創建方式,這裏不再贅述。

接下來在 resources 目錄下創建一個 applicationContext.xml 文件,並配置Spring 和 Jpa,如下:

<context:property-placeholder location="classpath:db.properties"/>
<context:component-scan base-package="org.javaboy"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
    <property name="driverClassName" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
</bean>
<bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory">
    <property name="dataSource" ref="dataSource"/>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    </property>
    <property name="packagesToScan" value="org.javaboy.model"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</prop>
        </props>
    </property>
</bean>
<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 配置jpa -->
<jpa:repositories base-package="org.javaboy.dao"
                  entity-manager-factory-ref="entityManagerFactory"/>

這裏和 Jpa 相關的配置主要是三個:

  • 一個是 entityManagerFactory
  • 一個是 Jpa 的事務
  • 一個是配置 dao 的位置

配置完成后,就可以在 org.javaboy.dao 包下創建相應的 Repository 了,如下:

public interface UserDao extends Repository<User, Long> {
    User getUserById(Long id);
}

getUserById 表示根據 id 去查詢 User 對象,只要我們的方法名稱符合類似的規範,就不需要寫 SQL,具體的規範一會來說。好了,接下來,創建 Service 和 Controller 來調用這個方法,如下:

@Service
@Transactional
public class UserService {
    @Resource
    UserDao userDao;

    public User getUserById(Long id) {
        return userDao.getUserById(id);
    }
}
public void test1() {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = ctx.getBean(UserService.class);
    User user = userService.getUserById(1L);
    System.out.println(user);
}

這樣,就可以查詢到 id 為 1 的用戶了。

2.2.2 Repository

上文我們自定義的 UserDao 實現了 Repository 接口,這個 Repository 接口是什麼來頭呢?

首先來看 Repository 的一個繼承關係圖:

可以看到,實現類不少。那麼到底如何理解 Repository 呢?

  1. Repository 接口是 Spring Data 的一個核心接口,它不提供任何方法,開發者需要在自己定義的接口中聲明需要的方法 public interface Repository<T, ID extends Serializable> { }
  2. 若我們定義的接口繼承了 Repository, 則該接口會被 IOC 容器識別為一個 Repository Bean,進而納入到 IOC 容器中,進而可以在該接口中定義滿足一定規範的方法。
  3. Spring Data可以讓我們只定義接口,只要遵循 Spring Data 的規範,就無需寫實現類。
  4. 與繼承 Repository 等價的一種方式,就是在持久層接口上使用 @RepositoryDefinition 註解,併為其指定 domainClass 和 idClass 屬性。像下面這樣:
@RepositoryDefinition(domainClass = User.class, idClass = Long.class)
public interface UserDao
{
    User findById(Long id);
    List<User> findAll();
}

基礎的 Repository 提供了最基本的數據訪問功能,其幾個子接口則擴展了一些功能,它的幾個常用的實現類如下:

  • CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法
  • PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法
  • JpaRepository: 繼承 PagingAndSortingRepository,實現一組 JPA 規範相關的方法
  • 自定義的 XxxxRepository 需要繼承 JpaRepository,這樣的 XxxxRepository 接口就具備了通用的數據訪問控制層的能力。
  • JpaSpecificationExecutor: 不屬於Repository 體系,實現一組 JPA Criteria 查詢相關的方法

2.2.3 方法定義規範

2.2.3.1 簡單條件查詢

  • 按照 Spring Data 的規範,查詢方法以 find | read | get 開頭
  • 涉及條件查詢時,條件的屬性用條件關鍵字連接,要注意的是:條件屬性以首字母大寫

例如:定義一個 Entity 實體類:

class User{ 
   private String firstName; 
   private String lastName; 
}

使用 And 條件連接時,條件的屬性名稱與個數要與參數的位置與個數一一對應,如下:

findByLastNameAndFirstName(String lastName,String firstName);
  • 支持屬性的級聯查詢. 若當前類有符合條件的屬性, 則優先使用, 而不使用級聯屬性. 若需要使用級聯屬性, 則屬性之間使用 _ 進行連接.

查詢舉例:

  • 按照 id 查詢
User getUserById(Long id);
User getById(Long id);
  • 查詢所有年齡小於 90 歲的人
List<User> findByAgeLessThan(Long age);
  • 查詢所有姓趙的人
List<User> findByUsernameStartingWith(String u);
  • 查詢所有姓趙的、並且 id 大於 50 的人
List<User> findByUsernameStartingWithAndIdGreaterThan(String name, Long id);
  • 查詢所有姓名中包含”上”字的人
List<User> findByUsernameContaining(String name);
  • 查詢所有姓趙的或者年齡大於 90 歲的
List<User> findByUsernameStartingWithOrAgeGreaterThan(String name, Long age);
  • 查詢所有角色為 1 的用戶
List<User> findByRole_Id(Long id);

2.2.3.2 支持的關鍵字

支持的查詢關鍵字如下圖:

2.2.3.3 查詢方法流程解析

為什麼寫上方法名,JPA就知道你想幹嘛了呢?假如創建如下的查詢:findByUserDepUuid(),框架在解析該方法時,首先剔除 findBy,然後對剩下的屬性進行解析,假設查詢實體為Doc:

  1. 先判斷 userDepUuid (根據 POJO 規範,首字母變為小寫)是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,繼續第二步;
  2. 從右往左截取第一個大寫字母開頭的字符串(此處為 Uuid),然後檢查剩下的字符串是否為查詢實體的一個屬性,如果是,則表示根據該屬性進行查詢;如果沒有該屬性,則重複第二步,繼續從右往左截取;最後假設 user 為查詢實體的一個屬性;
  3. 接着處理剩下部分(DepUuid),先判斷 user 所對應的類型是否有depUuid屬性,如果有,則表示該方法最終是根據 “ Doc.user.depUuid” 的取值進行查詢;否則繼續按照步驟 2 的規則從右往左截取,最終表示根據 “Doc.user.dep.uuid” 的值進行查詢。
  4. 可能會存在一種特殊情況,比如 Doc 包含一個 user 的屬性,也有一個 userDep 屬性,此時會存在混淆。可以明確在屬性之間加上 “_” 以顯式表達意圖,比如 “findByUser_DepUuid()” 或者 “findByUserDep_uuid()”
  5. 還有一些特殊的參數:例如分頁或排序的參數:
Page<UserModel> findByName(String name, Pageable pageable);
List<UserModel> findByName(String name, Sort sort);

2.2.3.4 @Query 註解

有的時候,這裏提供的查詢關鍵字並不能滿足我們的查詢需求,這個時候就可以使用 @Query 關鍵字,來自定義查詢 SQL,例如查詢 Id 最大的 User:

@Query("select u from t_user u where id=(select max(id) from t_user)")
User getMaxIdUser();

如果查詢有參數的話,參數有兩種不同的傳遞方式,

  • 利用下標索引傳參,索引參數如下所示,索引值從1開始,查詢中 ”?X” 個數需要與方法定義的參數個數相一致,並且順序也要一致:
@Query("select u from t_user u where id>?1 and username like ?2")
List<User> selectUserByParam(Long id, String name);
  • 命名參數(推薦):這種方式可以定義好參數名,賦值時採用@Param(“參數名”),而不用管順序:
@Query("select u from t_user u where id>:id and username like :name")
List<User> selectUserByParam2(@Param("name") String name, @Param("id") Long id);

查詢時候,也可以是使用原生的 SQL 查詢,如下:

@Query(value = "select * from t_user",nativeQuery = true)
List<User> selectAll();

2.2.3.5 @Modifying 註解

涉及到數據修改操作,可以使用 @Modifying 註解,@Query 與 @Modifying 這兩個 annotation 一起聲明,可定義個性化更新操作,例如涉及某些字段更新時最為常用,示例如下:

@Modifying
@Query("update t_user set age=:age where id>:id")
int updateUserById(@Param("age") Long age, @Param("id") Long id);

注意:

  1. 可以通過自定義的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
  2. 方法的返回值應該是 int,表示更新語句所影響的行數
  3. 在調用的地方必須加事務,沒有事務不能正常執行
  4. 默認情況下, Spring Data 的每個方法上有事務, 但都是一個只讀事務. 他們不能完成修改操作

說到這裏,再來順便說說 Spring Data 中的事務問題:

  1. Spring Data 提供了默認的事務處理方式,即所有的查詢均聲明為只讀事務。
  2. 對於自定義的方法,如需改變 Spring Data 提供的事務默認方式,可以在方法上添加 @Transactional 註解。
  3. 進行多個 Repository 操作時,也應該使它們在同一個事務中處理,按照分層架構的思想,這部分屬於業務邏輯層,因此,需要在Service 層實現對多個 Repository 的調用,並在相應的方法上聲明事務。

好了,關於Spring Data Jpa 本文就先說這麼多。

關注公眾號【江南一點雨】,專註於 Spring Boot+微服務以及前後端分離等全棧技術,定期視頻教程分享,關注后回復 Java ,領取松哥為你精心準備的 Java 乾貨!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

德國龐巴迪研發出200-400千瓦無線充電系統 未來將引入中國市場

龐巴迪2009年開始研發無線充電系統PRIMOVE,於2013年將該系統成功應用在純電動巴士無線充電中。截至2015年底已在德國曼海姆、柏林、布勞恩斯魏克市開通了商業運行線路,累計運行里程已超過12.5萬公里。目前,龐巴迪已研發生產出200-400千瓦無線充電系統PRIMOVE。未來,龐巴迪將把這個無線充電技術引入中國市場。

目前,中國電動巴士使用的無線充電系統是單組30千瓦或使用兩組並聯達到60千瓦充電功率。龐巴迪的這套大功率無線充電系統的充電速度遠遠超過了目前中國純電動巴士廣泛使用的80千瓦有線充電系統,使得無線充電不僅可以在城市微循環線路中使用,也同樣適應於骨幹公交線路 (例如BRT) 以及有軌電車等軌道交通。

為了將這項技術產業化並推向市場,擁有專利技術的地埋式初級線圈預製模組已經經過了長達一年的一百萬次的13噸軸重碾壓試驗,以此保證模組至少20年的使用壽命。該系統所有電力電子設備全部採取地埋的特點,使城市核心區充電基礎設施零占地成為可能。所有電力電子設備達到最高防護等級IP68,並採用德國專業的排水箱體設計,即使在暴雨情況下依然保證安全運行,成為這套無線充電系統的優勢所在。

獨特的線圈設計將電磁場定向傳輸,使巴士車內幾乎檢測不到電磁場,車外電磁場強度也只有國際標準ICNIRP2010 的四分之一。目前,PRIMOVE無線充電技術處於國際電動車用無線充電標準IEC61980中的最大功率等級。
 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

馬斯克承認低油價不利電動車銷售,但稱特斯拉較不受影響

油價直直落,美國人大買耗油車,這股風潮豈不是對電動車相當不利?特斯拉(Tesla)執行長艾隆‧馬斯克(Elon Musk)於 2016 年 1 月底受邀前往香港 StartmeupHK 新創論壇(StartmeupHK Venture Forum)擔任演講來賓,在香港受 CNN 訪問時,承認低油價對整體電動車市場不利,但認為特斯拉將比其他電動車對手更不受影響。

馬斯克承認低油價對電動車的不利影響,表示這符合經濟常識。事實上,低油價已經使得 2015 年美國車市燒油較兇的貨卡以及休旅車大行其道,就更大的市場方向來說,油價的經濟考量,也顯然會對消費者在傳統燃油車、油電混合車,以及全電動車之間的選擇發生影響力。   不過馬斯克認為,特斯拉受到的影響將較其他電動車對手小,因為特斯拉將本身定位於高檔市場,因此其消費者購車時的經濟考量程度較輕,相對的,馬斯克認為以低價搶市的電動車對手,面對低油價讓燃油車更為經濟的情況,將會大受打擊,他表示,有些電動車和燃油車車款除了動力來源並沒有基本差異,如此一來,消費者只會比較經濟面,這樣的電動車對手將會在低油價下受到重大影響,但是馬斯克認為就算低油價讓燃油車更為經濟,特斯拉的 Model 3 仍然會表現良好。Model 3 是特斯拉較為大眾取向的車款,車價 3.4 萬美元,相當於約 114 萬元新台幣。   馬斯克也歡迎其他車廠共襄盛舉,一同推出更多電動車,可把餅做大,如通用汽車(GM)的雪佛蘭(Chevrolet)Bolt,馬斯克表示,若通用汽車或其他車廠開發出讓消費者滿意的電動車,對世界是好事,他會歡迎它們。   馬斯克也談及中國市場,表示中國電動車市場雖然只是剛開始起步,但正朝向正確的方向邁進,他表示有些中國公司,指巴菲特投資的比亞迪,正朝著製造出性能良好電動車的方向前進,進展快速。不過,特斯拉本身在中國市場表現並不理想,要如何在中國建立生產線,特斯拉還在嘗試中,而身為進口車,特斯拉要蒙受高額進口稅,且無法得到中國政府對電動車的補貼,因此受到相當大的阻礙,一旦能在中國本地生產,將對生意有相當大助益。   馬斯克認為中國政府政策將不可避免的往電動車傾斜,以減輕空氣污染與碳排放,中國政府也了解電動車對未來相當重要,想讓城市擁有潔淨空氣,就必須發展電動車。馬斯克認為 Model 3 終有一天會在中國生產,不過可能要 3 年才能辦得到這點。

(首圖來源:)    (本文授權轉載自《》─〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

※小三通物流營運型態?

※快速運回,大陸空運推薦?

為什麼 HTTPS 比 HTTP 安全

HTTP(超文本傳輸協議)是目前互聯網應用最廣泛的協議,伴隨着人們網絡安全意識的加強,HTTPS 被越來越多地採納。不論是訪問一些購物網站,或是登錄一些博客、論壇等,我們都被 HTTPS 保護着,甚至 Google Chrome、Firefox 等主流瀏覽器已經將所有基於 HTTP 的站點都標記為不安全。

為什麼 HTTPS 比 HTTP 安全?在回答這個問題之前,首先我們得了解 HTTP 和 HTTPS 是什麼。

HTTP 和 HTTPS 的訪問過程

從互聯網發展至今,HTTP 一直擔任互聯網傳輸信息的標準協議。傳輸的信息可以是互聯網內計算機之間的文檔,文件,圖像,視頻等。

HTTP 請求過程中,客戶端與服務器之間沒有任何身份確認的過程,數據全部明文傳輸,“裸奔”在互聯網上,所以很容易遭到黑客的攻擊。

從上圖中可以看到,客戶端發出的請求很容易被黑客截獲,如果此時黑客冒充服務器,則其可返回任意信息給客戶端,而不被客戶端察覺,所以我們經常會聽到一詞“劫持”。

而 HTTPS 實際上是帶有 SSL 的 HTTP(HTTP + SSL=HTTPS)。當您在瀏覽器的地址欄中看到 HTTPS 時,這就意味着與該網站的所有通信都將被加密,整個訪問過程更加安全。

為什麼 HTTPS 比 HTTP 安全

HTTPS 的安全性往往體現在三個方面:

  • 服務器身份驗證,通過服務器身份驗證,用戶可以明確當前它正在與對應的服務器進行通信。

  • 數據機密性,其他方無法理解發送的數據內容,因為提交的數據是加密的。

  • 數據完整性,傳輸會攜帶 Message Authentication Code(MAC)用於驗證,因此傳輸的數據不會被另一方更改。
    可以舉個例子來比較下。一個 HTTP 請求,其組成則是多個遵循 HTTP 協議的文本行,例如下面的 GET 請求:

GET /helloupyun.txt HTTP/1.1

User-Agent: curl/7.73.0 libcurl/7.73.0 OpenSSL/1.1.l zlib/1.2.11

Host: www.upyun.com

Accept-Language: en

請求會以明文的形式直接發送,既然是明文的形式,對於協議命令和語法有基本了解的人,只要監控了請求發送的過程,就能獲取並讀懂請求的意義。因此用 HTTP 的方式發送密碼一類的數據時,安全性極低。

相對的,HTTPS 使用了 SSL(或 TLS)來加密 HTTP 請求和響應,因此在上面的示例中,監控請求的人將會看到一串隨機的数字,而不是可讀性的文本。

GsERHg9YDMpYk0VVDiRvw1H5miNieJeJ/FNUjgH0BmVAWII6+T4MnDwmCMZUI/orxP3HGwYCSIvyzS3MpmmSe4iaWKCOHH==

其中加密過程採用的 SSL(安全套接字層)這一標準的安全技術,涵蓋了非對稱密鑰和對稱密鑰。

對稱加密
對稱加密是指加密與解密使用同一個密鑰的加密算法。

目前常見的加密算法有:DES、AES、IDEA 等

非對稱加密
非對稱加密使用的是兩個密鑰,公鑰與私鑰,我們會使用公鑰對網站賬號密碼等數據進行加密,再用私鑰對數據進行解密。這個公鑰會發給查看網站的所有人,而私鑰是只有網站服務器自己擁有的。

目前常見非對稱加密算法:RSA,DSA,DH 等。

而常用的套件,例如 ChaCha20-Poly1305 加密套件就使用了這兩種算法,其中 Chacha20 是指對稱加密算法,而Poly1305 是指身份認證算法。

參考 RFC 文檔,我們可以了解 ChaCha20 提供了 256 位的加密強度,這作為對稱加密算法來保障 HTTPS 安全性是足夠了。

而 Poly1305 作為身份認證算法提供身份驗證,可以防止攻擊者在 TLS 握手過程中,將虛假信息插入到安全的數據流中,Poly1305 算法提供了大約 100 位的安全性加密強度,足以阻止這類攻擊。

總的來看,HTTPS 相比 HTTP ,它作為一種加密手段不僅加密了數據,還給了網站一張安全可信賴的身份證。

聊聊 HTTPS 的一些優缺點

整體來看 HTTPS 有以下五個優點:

  • 最大限度地提高 Web 上數據和事務的安全性;

  • 加密用戶敏感或者機密信息;

  • 提高搜索引擎中的排名

  • 避免在瀏覽器中出現“不安全”的提示;

  • 提升用戶對網站的信賴。

相對的,缺點也是必不可少的:

  • HTTPS 協議在握手階段耗時相對較大,會影響頁面整體加載速度;

  • 在瀏覽器和服務器上會更多的 CPU 周期來加密/解密數據;

  • SSL 證書一般都需要支付一定費用來獲取,並且費用往往不低;

  • 並不是絕對意義上的安全,在網站遭受攻擊,服務器被劫持時,HTTPS 基本起不到任何安全防護作用。

將 HTTP 升級成 HTTPS

如何將網站從 HTTP 升級成 HTTPS 呢?相比起常規的升級步驟,又拍雲提供一套更為簡潔明了的流程,從 SSL 證書的申購、管理到部署,三步即可完成。同時,又拍雲與國際頂級 CA 機構合作,證書類型豐富,操作流程簡單方便。

推薦閱讀

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

使用JaCoCo Maven插件創建代碼覆蓋率報告

這篇博客文章描述了我們如何使用JaCoCo Maven插件為單元和集成測試創建代碼覆蓋率報告。

我們的構建要求如下:

運行測試時,我們的構建必須為單元測試和集成測試創建代碼覆蓋率報告。
代碼覆蓋率報告必須在單獨的目錄中創建。換句話說,必須將用於單元測試的代碼覆蓋率報告創建到與用於集成測試的代碼覆蓋率報告不同的目錄中。
讓我們開始吧。

配置JaCoCo Maven插件

我們使用JaCoCo Maven插件有兩個目的:

  • 它使我們可以訪問JaCoCo運行時代理,該代理記錄了執行覆蓋率數據。
  • 它根據JaCo​​Co運行時代理記錄的執行數據創建代碼覆蓋率報告。
  • 我們可以按照以下步驟配置JaCoCo Maven插件:

將JaCoCo Maven插件添加到我們的POM文件的插件部分。

  • 為單元測試配置代碼覆蓋率報告。
  • 配置代碼覆蓋率報告以進行集成測試。
    下面將更詳細地描述這些步驟。

將JaCoCo Maven插件添加到POM文件

通過將以下插件聲明添加到其“ 插件”部分,我們可以將JaCoCo Maven插件添加到我們的POM文件中:

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.6.3.201306030806</version>
</plugin>

配置單元測試的代碼覆蓋率報告

我們可以通過將兩個執行添加到插件聲明中來為單元測試配置代碼覆蓋率報告。這些執行方式如下所述:

  • 第一次執行將創建一個指向JaCoCo運行時代理的屬性。確保執行數據已寫入文件target / coverage-reports / jacoco-ut.exec。將該屬性的名稱設置為surefireArgLine。運行單元測試時,此屬性的值作為VM參數傳遞。
  • 運行單元測試后,第二次執行將為單元測試創建代碼覆蓋率報告。確保從文件target / coverage-reports / jacoco-ut.exec中讀取執行數據,並將代碼覆蓋率報告寫入目錄target / site / jacoco-ut中。

我們的插件配置的相關部分如下所示:

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.6.3.201306030806</version>
    <executions>
        <!--
           Prepares the property pointing to the JaCoCo runtime agent which
           is passed as VM argument when Maven the Surefire plugin is executed.
       -->
        <execution>
            <id>pre-unit-test</id>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
                <!--
                   Sets the name of the property containing the settings
                   for JaCoCo runtime agent.
               -->
                <propertyName>surefireArgLine</propertyName>
            </configuration>
        </execution>
        <!--
           Ensures that the code coverage report for unit tests is created after
           unit tests have been run.
       -->
        <execution>
            <id>post-unit-test</id>
            <phase>test</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
                <!-- Sets the output directory for the code coverage report. -->
                <outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

讓我們找出如何為集成測試配置代碼覆蓋率報告。

配置集成測試的代碼覆蓋率報告

我們可以通過在插件聲明中添加兩個執行來為集成測試配置代碼覆蓋率報告。這些執行方式如下所述:

  • 第一次執行將創建一個指向JaCoCo運行時代理的屬性。確保將執行數據寫入文件target / coverage-reports / jacoco-it.exec。將該屬性的名稱設置為failsafeArgLine。運行我們的集成測試時,此屬性的值作為VM參數傳遞。
  • 創建一個執行,該執行在集成測試運行後為集成測試創建代碼覆蓋率報告。確保從文件target / coverage-reports / jacoco-it.exec中讀取執行數據,並將代碼覆蓋率報告寫入目錄target / site / jacoco-it。

我們的插件配置的相關部分如下所示:

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.6.3.201306030806</version>
    <executions>
        <!-- The Executions required by unit tests are omitted. -->
        <!--
           Prepares the property pointing to the JaCoCo runtime agent which
           is passed as VM argument when Maven the Failsafe plugin is executed.
       -->
        <execution>
            <id>pre-integration-test</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <destFile>${project.build.directory}/coverage-reports/jacoco-it.exec</destFile>
                <!--
                   Sets the name of the property containing the settings
                   for JaCoCo runtime agent.
               -->
                <propertyName>failsafeArgLine</propertyName>
            </configuration>
        </execution>
        <!--
           Ensures that the code coverage report for integration tests after
           integration tests have been run.
       -->
        <execution>
            <id>post-integration-test</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <dataFile>${project.build.directory}/coverage-reports/jacoco-it.exec</dataFile>
                <!-- Sets the output directory for the code coverage report. -->
                <outputDirectory>${project.reporting.outputDirectory}/jacoco-it</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

現在,我們已經配置了JaCoCo Maven插件。下一步是配置Maven Surefire插件。讓我們找出如何做到這一點。

配置Maven Surefire插件

我們使用Maven Surefire插件運行示例應用程序的單元測試。因為我們要為單元測試創​​建代碼覆蓋率報告,所以我們必須確保在運行單元測試時JaCoCo代理正在運行。我們可以通過添加的價值保證本surefireArgLine財產作為價值argLine配置參數。

Maven Surefire插件的配置如下所示(突出显示了所需的更改):

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.15</version>
    <configuration>
        <!-- Sets the VM argument line used when unit tests are run. -->
        <argLine>${surefireArgLine}</argLine>
        <!-- Skips unit tests if the value of skip.unit.tests property is true -->
        <skipTests>${skip.unit.tests}</skipTests>
        <!-- Excludes integration tests when unit tests are run. -->
        <excludes>
            <exclude>**/IT*.java</exclude>
        </excludes>
    </configuration>
</plugin>

我們快完成了。剩下要做的就是配置Maven Failsafe插件。讓我們找出如何做到這一點。

配置Maven故障安全插件

我們的示例應用程序的集成測試由Maven Failsafe插件運行。因為我們要為集成測試創建代碼覆蓋率報告,所以我們必須確保在運行集成測試時JaCoCo代理正在運行。我們可以通過將failsafeArgLine屬性的值添加為argLine配置參數的值來實現。

Maven Failsafe插件的配置如下所示(突出显示了所需的更改):

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.15</version>
    <executions>
        <!--
            Ensures that both integration-test and verify goals of the Failsafe Maven
            plugin are executed.
        -->
        <execution>
            <id>integration-tests</id>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
            <configuration>
                <!-- Sets the VM argument line used when integration tests are run. -->
                <argLine>${failsafeArgLine}</argLine>
                <!--
                    Skips integration tests if the value of skip.integration.tests property
                    is true
                -->
                <skipTests>${skip.integration.tests}</skipTests>
            </configuration>
        </execution>
    </executions>
</plugin>

創建代碼覆蓋率報告

現在,我們已成功完成所需的配置。讓我們看看如何為單元測試和集成測試創建代碼覆蓋率報告。

此博客文章的示例應用程序具有三個構建配置文件,下面對此進行了描述:

  • 在開發配置文件開發過程中使用,這是我們構建的默認配置文件。當此配置文件處於活動狀態時,僅運行單元測試。
  • 在集成測試配置文件用於運行集成測試。
  • 在所有的測試配置文件用於為運行單元測試和集成測試。
    我們可以通過在命令提示符處運行以下命令來創建不同的代碼覆蓋率報告:

  • 命令mvn clean test運行單元測試,併為目錄target / site / jacoco-ut創建單元測試的代碼覆蓋率報告。
  • 命令mvn clean verify -P integration-test運行集成測試,併為目錄target / site / jacoco-it創建用於集成測試的代碼覆蓋率報告。
  • 命令mvn clean verify -P all-tests運行單元測試和集成測試,併為單元測試和集成測試創建代碼覆蓋率報告。

技術類文章精選

非技術文章精選

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

※小三通物流營運型態?

※快速運回,大陸空運推薦?

PHP讀取Excel內的圖片

今天接到了一個從Excel內讀取圖片的需求,在網上查找了一些資料,基本實現了自己的需求,不過由於查到的一些代碼比較久遠,裏面一些庫方法已經被移除不存在了,所以不能直接移植到自己的項目里,需要稍加改動一下。

這裏介紹一下分別使用phpspreadsheet和PHPExcel擴展庫來實現讀取Excel內圖片的功能:

PHPSpreadsheet

首先安裝phpspreadsheet,由於線上服務器PHP版本是PHP5.6,所以需要安裝兼容PHP5.6的版本,這裏安裝1.8.2版本

composer require phpoffice/phpspreadsheet=1.8.2

然後就可以在項目里使用了

use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;

$imageFilePath = './uploads/imgs/'; //圖片本地存儲的路徑
if (!file_exists($imageFilePath)) { //如果目錄不存在則遞歸創建
    mkdir($imageFilePath, 0777, true);
}

try {
    $inputFileName = './files/1.xlsx';  //包含圖片的Excel文件
    $objRead = IOFactory::createReader('Xlsx');
    $objSpreadsheet = $objRead->load($inputFileName);
    $objWorksheet = $objSpreadsheet->getSheet(0);
    $data = $objWorksheet->toArray();

    foreach ($objWorksheet->getDrawingCollection() as $drawing) {
        list($startColumn, $startRow) = Coordinate::coordinateFromString($drawing->getCoordinates());
        $imageFileName = $drawing->getCoordinates() . mt_rand(1000, 9999);

        switch ($drawing->getExtension()) {
            case 'jpg':
            case 'jpeg':
                $imageFileName .= '.jpg';
                $source = imagecreatefromjpeg($drawing->getPath());
                imagejpeg($source, $imageFilePath . $imageFileName);
                break;
            case 'gif':
                $imageFileName .= '.gif';
                $source = imagecreatefromgif($drawing->getPath());
                imagegif($source, $imageFilePath . $imageFileName);
                break;
            case 'png':
                $imageFileName .= '.png';
                $source = imagecreatefrompng($drawing->getPath());
                imagepng($source, $imageFilePath, $imageFileName);
                break;
        }
        $startColumn = ABC2decimal($startColumn);
        $data[$startRow-1][$startColumn] = $imageFilePath . $imageFileName;
    }
    dump($data);die();
} catch (\Exception $e) {
    throw $e;
}

public function ABC2decimal($abc)
{
    $ten = 0;
    $len = strlen($abc);
    for($i=1;$i<=$len;$i++){
        $char = substr($abc,0-$i,1);//反向獲取單個字符

        $int = ord($char);
        $ten += ($int-65)*pow(26,$i-1);
    }
    return $ten;
}

可以看到,圖片被讀取並存到了本地服務器中

PHPExcel

PHPExcel實現從Excel文件里讀取內容的方法和phpspreadsheet幾乎一樣,畢竟phpspreadsheet就是在PHPExcel基礎上寫的,不過PHPExcel由於已經被廢棄了,所以建議優先使用phpspreadsheet,如果原來項目里一直使用了PHPExcel也可以繼續使用PHPExcel的方法

use PHPExcel_IOFactory;
use PHPExcel_Cell;

try {
    $inputFileName = './files/1.xlsx';
    $inputFileType = PHPExcel_IOFactory::identify($inputFileName);
    $objReader = PHPExcel_IOFactory::createReader($inputFileType);
    $objPHPExcel = $objReader->load($inputFileName);
} catch (\Exception $e) {
    die('加載文件發生錯誤:"'.pathinfo($inputFileName,PATHINFO_BASENAME).'": '.$e->getMessage());
}

$sheet = $objPHPExcel->getSheet(0);
$data = $sheet->toArray(); //該方法讀取不到圖片,圖片需單獨處理
$imageFilePath = './uploads/imgs/'; //圖片本地存儲的路徑
if (!file_exists($imageFilePath)) {
    mkdir($imageFilePath, 0777, true);
}

//處理圖片
foreach ($sheet->getDrawingCollection() as $img) {
    list($startColumn, $startRow) = PHPExcel_Cell::coordinateFromString($img->getCoordinates()); //獲取圖片所在行和列
    $imageFileName = $img->getCoordinates() . mt_rand(1000, 9999);
    switch($img->getExtension()) {
        case 'jpg':
        case 'jpeg':
            $imageFileName .= '.jpeg';
            $source = imagecreatefromjpeg($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
        case 'gif':
            $imageFileName .= '.gif';
            $source = imagecreatefromgif($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
        case 'png':
            $imageFileName .= '.png';
            $source = imagecreatefrompng($img->getPath());
            imagejpeg($source, $imageFilePath.$imageFileName);
            break;
    }
    $startColumn = ABC2decimal($startColumn);
    $data[$startRow-1][$startColumn] = $imageFilePath . $imageFileName;

}
var_dump($data);

public function ABC2decimal($abc)
{
    $ten = 0;
    $len = strlen($abc);
    for($i=1;$i<=$len;$i++){
        $char = substr($abc,0-$i,1);//反向獲取單個字符

        $int = ord($char);
        $ten += ($int-65)*pow(26,$i-1);
    }
    return $ten;
}

參考文章:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

Kubernetes概述

1. Kubernetes是什麼

Kubernetes是一個可移植的、可擴展的、用於管理容器化工作負載和服務的開源平台,它簡化(促進)了聲明式配置和自動化。它有一個龐大的、快速增長的生態系統。Kubernetes的服務、支持和工具隨處可見。

Kubernetes一詞源於希臘語,意為舵手或飛行員。2014年,谷歌開放了Kubernetes項目的源代碼。Kubernetes基於谷歌在大規模運行生產工作負載方面的15年經驗,以及來自社區的最佳想法和實踐。

使用 Kubernetes, 我們可以快速高效地響應客戶需求:

  • 快速、可預測地部署你的應用程序
  • 擁有即時擴展應用程序的能力
  • 不影響現有業務的情況下,無縫地發布新功能
  • 優化硬件資源,降低成本

Kubernetes的目標是構建一個軟件和工具的生態系統,以減輕你在公共雲或私有雲運行應用程序的負擔。

1.1. 時光倒流

讓我們把時間回溯到從前,看看為什麼Kubernetes是如此有用。

傳統部署時代:在早期,組織在物理服務器上運行應用程序。無法為物理服務器中的應用程序定義資源邊界,這導致了資源分配問題。例如,如果一個物理服務器上運行多個應用程序,那麼在某些情況下,一個應用程序將佔用大部分資源,從而導致其他應用程序性能下降。對此的解決方案是在不同的物理服務器上運行每個應用程序。但是,由於資源沒有得到充分利用,而且維護許多物理服務器的成本很高,所以無法進行擴展。

虛擬化部署時代:作為一種解決方案,引入了虛擬化。它允許你在單個物理服務器的CPU上運行多個虛擬機(vm)。虛擬化允許在vm之間隔離應用程序,並提供一定程度的安全性,因為一個應用程序的信息不能被另一個應用程序自由訪問。

虛擬化可以更好地利用物理服務器中的資源,並提供更好的可伸縮性,因為可以方便地添加或更新應用程序,從而降低硬件成本,等等。通過虛擬化,你可以將一組物理資源表示為一次性虛擬機集群。

每個VM是一個完整的機器,運行所有組件,包括它自己的操作系統,運行在虛擬硬件之上。

容器部署時代:容器類似於vm,但是它們具有寬鬆的隔離屬性,以便在應用程序之間共享操作系統(OS)。因此,容器被認為是輕量級的。與VM類似,容器有自己的文件系統、CPU、內存、進程空間等等。由於它們與底層基礎設施解耦,因此可以跨雲和OS發行版移植。

容器已經變得很流行,因為它們提供了額外的好處,比如:

  • 敏捷應用程序創建和部署:與使用VM鏡像相比,增加了容器鏡像創建的方便性和效率
  • 持續開發、集成和部署:提供可靠且頻繁的容器鏡像構建和部署,具有快速且輕鬆的回滾
  • 開發和運維關注點分離:在構建/發布時而不是部署時創建應用程序容器鏡像,從而將應用程序與基礎設施分離
  • 可觀察性:不僅能显示操作系統級的信息和指標,還能显示應用程序的健康狀況和其他信號
  • 跨開發、測試和生產的環境一致性:在筆記本電腦上運行與在雲上運行完全相同
  • 雲和操作系統發布的可移植性:運行在Ubuntu, RHEL, CoreOS, on-prem,谷歌Kubernetes引擎,和其他任何地方
  • 以應用程序為中心的管理:將抽象級別從在虛擬硬件上運行操作系統提高到使用邏輯資源在操作系統上運行應用程序
  • 鬆散耦合、分佈式、彈性、解放的微服務:應用程序被分解成更小的獨立部分,可以動態地部署和管理——而不是運行在一台大型單用途機器上的單片堆棧
  • 資源隔離:可預測的應用程序性能
  • 資源利用:效率高,密度大 

1.2. 為什麼需要Kubernetes?它能做什麼?

容器是捆綁和運行應用程序的好方法。在生產環境中,你需要管理運行應用程序的容器,並確保沒有停機時間。例如,如果一個容器發生故障,則需要啟動另一個容器。如果這個行為由一個系統來處理不是更容易嗎?

Kubernetes為你提供了一個能夠彈性地運行分佈式系統的框架。它負責處理應用程序的擴展和故障轉移,提供部署模式,等等。

Kubernetes為你提供:

服務發現和負載平衡

  Kubernetes可以使用DNS名稱或自己的IP地址公開容器。如果到容器的通信量很高,Kubernetes能夠實現負載平衡並分配網絡通信量,從而使部署保持穩定

存儲編排

  Kubernetes允許你自動掛載自己選擇的存儲系統,比如本地存儲、公有雲提供商等等

自動滾動更新和回滾

  可以使用Kubernetes描述所部署容器的期望狀態,並且可以以受控的速率將實際狀態更改為期望狀態

自動包裝

  你只需為Kubernetes提供一組節點,它便可以使用這些節點來運行容器化的任務。告訴Kubernetes每個容器需要多少CPU和內存(RAM)。Kubernetes可以在你的節點上放置容器,以充分利用你的資源。 

自動重啟 

  Kubernetes會重新啟動失敗的容器,替換容器,殺死不響應用戶定義的健康檢查的容器,並且在它們準備好服務之前不會將他們暴露給客戶端。

秘密和配置管理

  Kubernetes允許你存儲和管理敏感信息,比如密碼、OAuth令牌和ssh密鑰。你可以部署和更新秘密和應用程序配置,而無需重新構建容器映像,也無需在堆棧配置中公開秘密。 

1.3. Kubernetes不是什麼

Kubernetes不是一個傳統的、包羅萬象的PaaS(平台即服務)系統。由於Kubernetes是在容器級別而不是在硬件級別操作的,所以它提供了一些PaaS產品常見的通用特性,例如部署、擴展、負載平衡、日誌記錄和監視。但是,Kubernetes不是單片的,這些默認的解決方案是可選的和可插拔的。Kubernetes為構建開發人員平台提供了構建塊,但是在重要的地方保留了用戶的選擇和靈活性。

  • 不限制所支持的應用程序類型。Kubernetes的目標是支持非常多樣化的工作負載,包括無狀態、有狀態和數據處理工作負載。如果一個應用程序可以在容器中運行,那麼它應該可以在Kubernetes上運行。
  • 不部署源代碼,也不構建應用程序。持續集成、交付和部署(CI/CD)工作流由組織文化和偏好以及技術需求決定。
  • 不提供應用級服務,如中間件(例如消息總線)、數據處理框架(例如Spark)、數據庫(例如mysql)、緩存,也不提供集群存儲系統(例如Ceph)作為內置服務。這些組件可以運行在Kubernetes上,並且/或者可以由運行在Kubernetes上的應用程序通過可移植的機制(如Open Service Broker)訪問。
  • Kubernetes 允許用戶選擇其他的日誌記錄,監控和告警系統 
  • 不提供也不強制要求配置語言/系統(例如,jsonnet)。它提供了一個聲明性API,可以被任意形式的聲明性規範作為目標。
  • 不提供也不採用任何全面的機器配置、維護、管理或自修復系統。
  • 此外,Kubernetes不僅僅是一個編排系統。事實上,它消除了對編排的需要。編排的技術定義是執行一個已定義的工作流:首先執行a,然後執行B,然後執行c。相反,Kubernetes包括一套獨立、可組合的控制過程,通過聲明式語法使其連續地朝着期望狀態驅動當前狀態。你怎麼從A點到c點都不重要,只要告訴到C的狀態即可。 

2. Kubernetes組件

當你部署Kubernetes的時候,你會得到一個集群。

一個集群是一組機器,稱為節點(nodes),它們運行由Kubernetes管理的容器化應用程序。

一個集群至少有一個工作節點(worker node)和一個主節點(master node)。

工作節點承載應用程序的組件。主節點管理集群中的工作節點和pods。多個主節點用於提供具有故障轉移和高可用性的集群。

下面是Kubernetes集群的關係圖: 

2.1. Master組件

Master組件提供集群的控制面板。Master組件對集群做出全局決策(例如,調度),Master組件檢測並響應集群事件(例如,當部署的replicas字段不滿足時啟動一個新的pod)

Master組件可以在集群中的任何機器上運行。但是,為了簡單起見,設置腳本通常在同一台機器上啟動所有Master組件,並且不在這台機器上運行用戶容器。

kube-apiserver 

  API服務器是一個Kubernetes面板控制組件,用於公開Kubernetes API。它是Kubernetes控制面板的前端。

  Kubernetes API服務器的主要實現是kube-apiserver。kube-apiserver被設計成水平伸縮——也就是說,它通過部署更多實例來伸縮。你可以運行kube-apiserver的多個實例,並在這些實例之間平衡流量。

etcd 

  一致性和高可用的鍵值存儲用作Kubernetes的所有集群數據備份存儲。

  如果你的Kubernetes集群使用etcd作為其備份存儲,請確保對這些數據有備份計劃。

kube-scheduler

  監視沒有分配節點的新創建的pod,併為它們選擇要運行的節點。 

kube-controller-manager

  運行控制器的組件,從邏輯上講,每個控制器都是一個單獨的進程,但是為了降低複雜性,它們都被編譯成一個單一的二進制文件,並在一個進程中運行。

這些控制器包括:

  • Node Controller:節點控制器負責發現和響應節點故障
  • Replication Controller:副本控制器負責為系統中的每個副本控制器對象維護正確的pod數量
  • Endpoints Controller:端點控制器填充端點對象(即,連接服務和pod)
  • Service Account & Token Controllers:為新的名稱空間創建默認帳戶和API訪問令牌 

cloud-controller-manager 

  雲控制器-管理器,運行與底層雲提供商交互的控制器

2.2. Node組件

Node組件在每個節點上運行,維護運行的pods並提供Kubernetes運行時環境。 

kubelet

  kubelet是一個代理,它運行在集群中的每個節點上,它確保容器在一個pod中運行。kubelet只管理由Kubernetes創建的容器。 

kube-proxy

  kube-proxy是運行在集群中每個節點上的網絡代理,是實現Kubernetes服務概念的一部分。

  kube-proxy維護節點上的網絡規則。這些網絡規則允許從集群內外的網絡會話與pod進行網絡通信。

  如果有可用的操作系統包過濾層,kube-proxy將使用它。否則,kube-proxy將轉發流量本身。

Container Runtime

  容器運行時是負責運行容器的軟件。

  Kubernetes支持多個容器運行時:Docker、containerd、crio、rktlet和Kubernetes CRI(容器運行時接口)的任何實現。 

2.3. Addons(插件)

DNS

  雖然其他插件不是嚴格要求的,但所有Kubernetes集群都應該有集群DNS,因為許多示例都依賴於它。

Web UI (Dashboard)

  Dashboard是一個通用的、基於web的Kubernetes集群用戶界面。它允許用戶管理和排除集群中運行的應用程序和集群本身的故障。

Container Resource Monitoring

  容器資源監控記錄中央數據庫中容器的一般時間序列指標,並提供用於瀏覽該數據的UI。

Cluster-level Logging

  集群級別的日誌記錄機制負責將容器日誌保存到具有搜索/瀏覽界面的中央日誌存儲中。

3. 回顧&小結

Kubernetes:

  • 可移植: 無論公有雲、私有雲、混合雲還是多雲架構都全面支持
  • 可擴展: 它是模塊化、可插拔、可掛載、可組合的,支持各種形式的擴展
  • 自修復: 它可以自保持應用狀態、可自重啟、自複製、自縮放的,通過聲明式語法提供了強大的自修復能力

Kubernetes建立在Google公司15年的運維經驗基礎之上,Google所有的應用都運行在容器上 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置,台北網頁設計,各種案例分享

※小三通物流營運型態?

※快速運回,大陸空運推薦?

高性能Web動畫和渲染原理系列(5)合成層的生成條件和陷阱

目錄

示例代碼託管在:

博客園地址:

華為雲社區地址:

一. 硬件加速相關的幾個概念

之前介紹到了RenderLayer渲染層的概念,在涉及到硬件加速的話題時,出現了很多新的概念,參考《Webkit技術內幕》一書的介紹總結如下:

Webkit決定將哪些RenderLayer對象組合在一起,形成一個有後端存儲的新層,這一新層不久後會用於合成,這裏稱之為合成層(CompositingLayer)。每一個合成層都會對應一個或多個後端存儲,由RenderLayerBacking類進行統一管理,後端存儲空間使用GraphicsLayer來表示,也就是說RenderLayerBacking管理着一個或多個與對應的合成層有關的GraphicsLayer。

筆者旁白:對於渲染過程來說,只需要理解這裏形成了新的CompositingLayer合成層就可以了,其他的層概念基本都是用於實現對CompositingLayer功能支持的,概念數量太多對於理解宏觀流程是一大障礙。

二. 合成層的生成條件

顯式提升

合成層的處理是依賴於硬件加速的,但是GPU的存儲空間有限最好不要濫用,過多的合成層有可能還會造成相反的效果,所以瀏覽器只會將滿足下列任意條件的RenderLayer提升為CompositingLayer:

  • 具有CSS3D屬性或CSS透視效果
  • 包含的RenderObject節點表示的是使用硬件加速的視頻解碼技術的HTML5video元素
  • 包含的RenderObject節點包含使用了硬件加速的Canvas2D或WebGL技術
  • 使用了CSS透明效果或CSS變形動畫
  • 使用了硬件加速的CSS Filters技術(有的文獻中表示filters屬性並沒有提升為合成層的效果,推測只有一部分filters濾鏡效果需要使用硬件加速,並非所有)
  • 使用了剪裁Clip或者反射Reflection,並且它的後代中包含一個合成層
  • 擁有一個Z坐標比自己小的兄弟節點,且該節點是一個合成層。

上面的規則里我們最熟悉的可能就是transform:translateZ(0)或者在關鍵幀動畫的定義中改變transform和opacity屬性。當然,隨着技術的演進,上面的規則並不一定全面,Chromium官網提供的開發者演講PPT中也對提升的理由進行了相關的描述:

你可以在Chrome調試面板的【Layers】功能中對分層相關的結果進行檢視,查看哪些層進行了提升以及被提升的具體原因,避免出現與自己意圖相悖的層提升:

隱式提升

RenderLayer滿足特殊條件時被提升為CompositingLayer對開發者而言是比較可控的。但除此之外,在瀏覽器的合成階段,還存在隱式合成的狀況,一些特定的場景中出現的合成層並不是開發者主觀期望的。

隱式合成主要發生在元素出現重疊時,層級較低的元素如果被提升為合成層后,最終合成的結果就可能出現在原來比自己層級更高的元素之上,從而出現錯誤的堆疊關係,為了糾正這種關係,只能讓原本層級高(但是並不用提升為合成層的元素)發生提升也成為合成層。例如下面的代碼:

<div style="position:absolute;height:200px;width:200px;background-color: #DA5961;"></div>
<div style="position:absolute;left:30px;top:50px;height:200px;width:200px;background-color: #3498db;"></div>
<div style="position:absolute;left:60px;top:100px;height:200px;width:200px;background-color: #1abc9c;"></div>

三個div盒子堆疊在一起,可以看到它們都繪製在同一個層上(這裏的層並不與RenderLayer對應,畢竟它只是一个中間態的樹結構):

此時如果為最底下的紅色矩形添加transform:translateZ(0)屬性將其提升為合成層后,為了保證正確的堆疊關係,藍色和綠色的矩形就會被提升為合成層,代碼如下:

<div style="transform:translateZ(0);position:absolute;height:200px;width:200px;background-color: #DA5961;"></div>
<div style="position:absolute;left:30px;top:50px;height:200px;width:200px;background-color: #3498db;"></div>
<div style="position:absolute;left:60px;top:100px;height:200px;width:200px;background-color: #1abc9c;"></div>

藍色和綠色的矩形並沒有形成獨立的合成層,而是被壓縮在同一個合成層中:

從上圖中的細節信息中可以看到,提升的原因是layerFotSquashingContent,也就是為了保證堆疊順序的正確,用一個單獨的合成層來將受到影響的元素收集在一起,既保證堆疊順序,也避免在期望之外生成過多的合成層。如果調整綠色矩形的位置,就可以看到,當視覺上不存在覆蓋時,它就不需要提升了:

BUT!!!還沒完,最坑的部分來了,如果此時給藍色的div加上一點動畫,你會發現綠色div又被提升到了獨立的合成層上,儘管他們之間並沒有重疊區,但還是被提升了:

從圖中的合成原因可以看到:它可能和一個相鄰的合成層元素髮生交疊,所以被提升了。沒錯,就是“可能”。Fouber這篇中的示例更加詳細,子元素引發父元素提升,父元素又引發兄弟元素提升。

三. 硬件加速的權衡

所有的技術方案都是有代價的,這是亘古不變的道理,合成層的好處很明顯,GPU比CPU的處理速度快很多,觸發repaint重繪時,只需要重繪獨立的層,然後重新合成即可,不需要重繪整個畫面。但它也存在一些弊端:首先是數據傳輸的問題,CPU和GPU的關係就好比客戶端和服務端一樣,它們的協作是需要傳輸數據的,當層的數量達到一定量級后,傳輸的速度就會影響到整體的處理效率,進而導致在一些低中端設備上出現閃爍等現象;另外,每個合成層都具會佔據額外的內存,這個數量通常比開發者以為的要大的多,尤其是在移動端這種硬件資源受限制的場景中,過量的內存使用分分鐘就會讓應用崩潰。

四. 動畫實現的一些建議

  1. 使用transform實現動畫

    這可能是我們編寫動畫時聽到最多的建議了。例如使用left和top來實現位置動畫時,絕對定位的元素會形成RenderLayer,但是卻不符合提升為CompositingLayer的條件,所以動畫元素就會和Document處在同一個合成層里,持續進行的動畫就會導致Document這一層(通常是正常文檔流這一層,包含了大量的流式布局的元素)不斷重繪,從而影響渲染效率,如果能夠讓動畫的節點放到單獨的合成層里,就可以避免這種大規模重繪,並藉助GPU加速合成的能力加速整個渲染流程。

  2. 排查被動提升的情形

    被動提升主要是指“兄弟元素相對層級低於自己但卻是一個合成層”的情形以及“發生堆疊遮擋的幾個元素中層級較低的元素被提升為合成層”的狀況。一般的解決方案是主動提升動畫元素的z-index值或者調整文檔結構中節點的先後順序,當然所有的結果都還需要通過測試來確認。

  3. 考慮合成層的空間佔用

    合成層的後端存儲是渲染后的像素點數據,它的體積可能會非常大,在使用大屏圖片時需要盡可能將其壓縮至視覺可接受的範圍而不能一味追求高清,對於純色的元素,可以使用較小的尺寸並藉助transform:scale來放大至需要的尺寸。

  4. 實測為王

    任何方案都只是一種思路,必須通過在真實環境測試驗證才能確認其有效性。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

高通將研發動態無線充電技術 有望實現邊行駛邊充電

高通近日表示他們正在研發一種動態無線充電技術,可以緩解電動汽車行駛一半沒電的這種困擾,實現邊行駛汽車邊充電。

2014年的時候高通在首屆電動汽車方程式錦標賽中推出了Halo車用無線充電系統(靜態無線充電技術)。這個名為Halo的車用無線充電系統總共包含四個部分:供電組件、充電板、車載接收器和車載控制器。而它的原理是利用磁共振效應實現地面充電板與電動車充電板之間的電量傳輸,從而實現對純電動汽車或是混合型動力汽車的動力電池組進行非接觸式充電。

在利用Halo車用無線充電系統給汽車進行充電時的操作也相對簡單, Halo車用無線充電系統也成為靜態車用無線充電系統,高通表示在充電時只需將車輛停放在充電裝置上即可實現充電任務, Halo 車用無線充電系統的充電效率可以高達90%,堪稱絕對的高效,遠遠超過現階段的線纜充電裝備的充電效率。據悉Halo車用無線充電系統的功率可達20kW,一個配備85kWh電池組的特斯拉 MODEL S P85利用Halo車用無線充電系統充電整個過程也只需要不到五個小時的時間即可完成。

在談到Halo車用無線充電系統時,高通總裁Aberle表示這種技術將在未來的兩到三年即可問世,應用到當下的電動汽車中。目前高通表示他們正在和汽車製造商戴姆勒-克萊斯勒合作,準備將這種靜態無線充電系統應用到他們的汽車中。

而未來要推出的這種動態無線充電技術屬於Halo車用無線充電系統即靜態無線充電技術的升級版,為了實現這種邊行駛邊充電的目標,未來他們將會吧無線充電裝置安裝在路面地下,當電動汽車行駛經過含有充電裝置的路段時即可實現邊行駛邊充電。但由於這種技術需要對路面進行徹底的翻修重置,需要高昂的專案費用,因而短時間內還是不能實現邊行駛邊充電的目標。

其實高通並不是第一個因為這種原因拉長專案進程的公司,之前因特爾曾計畫在機場、火車站、餐館等公共場所為筆記本等智慧設備修建無線充電系統,但因為對基礎設施改造的工程量巨大和所需的項目費昂貴,項目始終進展緩慢,暫時毫無進展。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?

德國政府斥資20億歐元發力電動汽車

德國總理默克爾日前召見了幾位德國主要車企高管,討論電動汽車補貼政策問題。在此之前,德國經濟部長(西格馬•加布里爾)也承諾要拿出20億歐元來鼓勵消費者購買電動汽車,這些資金將主要用於消費者購買補貼、充電網路建設及鼓勵政府用車等方面。

具體政策,德國政府還要和汽車製造商繼續對話,最快也要等到3月才會制定完成。

儘管默克爾與汽車行業老闆們的會談還未達成一致,但對電動汽車補助政策的制定還是能起到極強的推動作用。根據路透社最新報導,“默克爾團隊考慮推行一項高達5000歐元的補助給電動車車主,但德國財長朔伊布勒表示反對。”

其實,從2009年以來,德國就一直通過鼓勵措施來推動新能源汽車的發展。2009年,德國內閣通過了具有重要戰略意義的電動汽車發展綱領性檔《國家電動汽車發展計畫》,該計畫確定了發展電動汽車的主要技術路線,以及德國發展電動汽車的目標;2011年,德國政府發佈了電動汽車政府行動計畫,提出將2013年年底前投入電動汽車研發的政府資助由之前規劃的10億歐元提高到20億歐元,並將推動建設3—4個國家級大規模示範專案;更在2014年通過了一項關於電動汽車的新法律,旨在提高德國全境電動汽車的使用率,該項法律從經濟補貼、擁有進入限制區域的權利、停車優惠、特殊標誌車牌、車道使用等多方面極大的保障了新能源汽車消費者的利益。而今年德國又將斥20億歐元出臺補貼政策,則更能看出德國政府對新能源汽車產業的重視。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

※小三通海運與一般國際貿易有何不同?

※小三通快遞通關作業有哪些?