JavaPersistenceWithMyBatis3笔记-第1章-001

一、介绍

1.项目结构

2.数据库结构

二、代码

1.Mapper

 1 package com.mybatis3.mappers;
 2 
 3 import java.util.List;
 4 
 5 import com.mybatis3.domain.Student;
 6 
 7 
 8 /**
 9  * @author Siva
10  *
11  */
12 public interface StudentMapper
13 {
14 
15     List<Student> findAllStudents();
16 
17     Student findStudentById(Integer id);
18 
19     void insertStudent(Student student);
20 
21     void updateStudent(Student student);
22 
23 }

StudentMapper.xml

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE mapper
 3   PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 4   "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 5   
 6 <mapper namespace="com.mybatis3.mappers.StudentMapper">
 7     
 8     <resultMap type="Student" id="StudentResult">
 9         <id     property="studId" column="stud_id"/>
10         <result property="name" column="name"/>
11         <result property="email" column="email"/>
12         <result property="dob" column="dob"/>
13     </resultMap>
14   
15       <select id="findAllStudents" resultMap="StudentResult">
16         select * from Students
17       </select>
18       
19       <select id="findStudentById" parameterType="int" resultType="Student">
20         select stud_id as studId, name, email, dob from Students where stud_id=#{studId}
21       </select>
22       
23       <insert id="insertStudent" parameterType="Student">
24           INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) VALUES(#{studId},#{name},#{email},#{dob})
25       </insert>
26       
27       <update id="updateStudent" parameterType="Student">
28           UPDATE STUDENTS SET NAME=#{name}, EMAIL=#{email}, DOB=#{dob} WHERE STUD_ID=#{studId}
29       </update>
30       
31 </mapper>

2.Service

 1 package com.mybatis3.services;
 2 
 3 import java.util.List;
 4 
 5 import org.apache.ibatis.session.SqlSession;
 6 import org.slf4j.Logger;
 7 import org.slf4j.LoggerFactory;
 8 
 9 import com.mybatis3.domain.Student;
10 import com.mybatis3.mappers.StudentMapper;
11 import com.mybatis3.util.MyBatisSqlSessionFactory;
12 
13 
14 /**
15  * @author Siva
16  *
17  */
18 public class StudentService 
19 {
20     private Logger logger = LoggerFactory.getLogger(getClass());
21     
22     public List<Student> findAllStudents()
23     {
24         SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
25         try {
26             StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
27             return studentMapper.findAllStudents();
28         } finally {
29             sqlSession.close();
30         }
31     }
32     
33     public Student findStudentById(Integer studId)
34     {
35         logger.debug("Select Student By ID :{}", studId);
36         SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
37         try {
38             StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
39             return studentMapper.findStudentById(studId);
40             //return sqlSession.selectOne("com.mybatis3.StudentMapper.findStudentById", studId);
41         } finally {
42             sqlSession.close();
43         }
44     }
45     
46     public void createStudent(Student student)
47     {
48         SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
49         try {
50             StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
51             studentMapper.insertStudent(student);
52             sqlSession.commit();
53         } finally {
54             sqlSession.close();
55         }
56     }
57     
58     public void updateStudent(Student student)
59     {
60         SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
61         try {
62             StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
63             studentMapper.updateStudent(student);
64             sqlSession.commit();
65         } finally {
66             sqlSession.close();
67         }
68     }
69 }

老式的JDBC

  1 package com.mybatis3.services;
  2 
  3 import java.sql.Connection;
  4 import java.sql.DriverManager;
  5 import java.sql.PreparedStatement;
  6 import java.sql.ResultSet;
  7 import java.sql.SQLException;
  8 import java.util.Date;
  9 
 10 import com.mybatis3.domain.Student;
 11 
 12 
 13 /**
 14  * @author Siva
 15  *
 16  */
 17 
 18 public class JdbcStudentService
 19 {
 20     
 21     private static final String DRIVER = "com.mysql.jdbc.Driver";
 22     private static final String URL = "jdbc:mysql://localhost:3306/elearning";
 23     private static final String USERNAME = "root";
 24     private static final String PASSWORD = "admin";
 25 
 26     public static void main(String[] args)
 27     {
 28         
 29         JdbcStudentService service = new JdbcStudentService();
 30         
 31         Student existingStudent = service.findStudentById(1);
 32         System.out.println(existingStudent);
 33         
 34         
 35         long ts = System.currentTimeMillis();//For creating unique student names
 36         Student newStudent = new Student(0,"student_"+ts,"student_"+ts+"@gmail.com",new Date());
 37         service.createStudent(newStudent);
 38         System.out.println(newStudent);
 39         
 40         int updateStudId = 3;
 41         Student updateStudent = service.findStudentById(updateStudId);
 42         ts = System.currentTimeMillis();//For creating unique student email
 43         updateStudent.setEmail("student_"+ts+"@gmail.com");
 44         service.updateStudent(updateStudent);
 45         
 46     }
 47     
 48     public Student findStudentById(int studId)
 49     {
 50         Student student = null;
 51         Connection conn = null;
 52         try
 53         {
 54             conn = getDatabaseConnection();
 55             String sql = "select * from students where stud_id=?";
 56             PreparedStatement pstmt = conn.prepareStatement(sql);
 57             pstmt.setInt(1, studId);
 58             ResultSet rs = pstmt.executeQuery();
 59             if(rs.next())
 60             {
 61                 student = new Student();
 62                 student.setStudId(rs.getInt("stud_id"));
 63                 student.setName(rs.getString("name"));
 64                 student.setEmail(rs.getString("email"));
 65                 student.setDob(rs.getDate("dob"));
 66             }
 67             
 68         } catch (SQLException e)
 69         {
 70             throw new RuntimeException(e);
 71         }
 72         finally
 73         {
 74             if(conn!= null){
 75                 try {
 76                     conn.close();
 77                 } catch (SQLException e){ }
 78             }
 79         }
 80         return student;
 81     }
 82     
 83     public void createStudent(Student student)
 84     {
 85         Connection conn = null;
 86         try
 87         {
 88             conn = getDatabaseConnection();
 89             String sql = "INSERT INTO STUDENTS(STUD_ID,NAME,EMAIL,DOB) VALUES(?,?,?,?)";
 90             PreparedStatement pstmt = conn.prepareStatement(sql);
 91             pstmt.setInt(1, student.getStudId());
 92             pstmt.setString(2, student.getName());
 93             pstmt.setString(3, student.getEmail());
 94             pstmt.setDate(4, new java.sql.Date(student.getDob().getTime()));
 95             pstmt.executeUpdate();
 96             
 97         } catch (SQLException e)
 98         {
 99             throw new RuntimeException(e);
100         }
101         finally
102         {
103             if(conn!= null){
104                 try {
105                     conn.close();
106                 } catch (SQLException e){ }
107             }
108         }
109     }
110     
111     public void updateStudent(Student student)
112     {
113         Connection conn = null;
114         try
115         {
116             conn = getDatabaseConnection();
117             conn = getDatabaseConnection();
118             String sql = "UPDATE STUDENTS SET NAME=?,EMAIL=?,DOB=? WHERE STUD_ID=?";
119             PreparedStatement pstmt = conn.prepareStatement(sql);
120             pstmt.setString(1, student.getName());
121             pstmt.setString(2, student.getEmail());
122             pstmt.setDate(3, new java.sql.Date(student.getDob().getTime()));
123             pstmt.setInt(4, student.getStudId());
124             pstmt.executeUpdate();
125             
126         } catch (SQLException e)
127         {
128             throw new RuntimeException(e.getCause());
129         }
130         finally
131         {
132             if(conn!= null){
133                 try {
134                     conn.close();
135                 } catch (SQLException e){ }
136             }
137         }
138     }
139     
140     protected Connection getDatabaseConnection() throws SQLException
141     {
142         try
143         {
144             Class.forName(JdbcStudentService.DRIVER);
145             return DriverManager.getConnection(JdbcStudentService.URL, 
146                                                 JdbcStudentService.USERNAME, 
147                                                 JdbcStudentService.PASSWORD);
148         } catch (SQLException e)
149         {
150             throw e;
151         } catch (Exception e)
152         {
153             throw new RuntimeException(e.getCause());
154         } 
155     }
156 
157 }

3.Domain

 1 package com.mybatis3.domain;
 2 
 3 import java.util.Date;
 4 
 5 
 6 /**
 7  * @author Siva
 8  *
 9  */
10 public class Student
11 {
12     private Integer studId;
13     private String name;
14     private String email;
15     private Date dob;
16     
17     public Student() {
18         
19     }
20     
21     public Student(Integer studId) {
22         this.studId = studId;
23     }
24     
25     public Student(Integer studId, String name, String email, Date dob) {
26         this.studId = studId;
27         this.name = name;
28         this.email = email;
29         this.dob = dob;
30     }
31 
32     @Override
33     public String toString() {
34         return "Student [studId=" + studId + ", name=" + name + ", email="
35                 + email + ", dob=" + dob + "]";
36     }
37     
38     public Integer getStudId() {
39         return studId;
40     }
41     public void setStudId(Integer studId) {
42         this.studId = studId;
43     }
44     public String getName() {
45         return name;
46     }
47     public void setName(String name) {
48         this.name = name;
49     }
50     public String getEmail() {
51         return email;
52     }
53     public void setEmail(String email) {
54         this.email = email;
55     }
56     public Date getDob() {
57         return dob;
58     }
59     public void setDob(Date dob) {
60         this.dob = dob;
61     }
62     
63     
64 }

4.辅助类

 1 package com.mybatis3.util;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.sql.Connection;
 6 import java.sql.DriverManager;
 7 import java.util.Properties;
 8 
 9 import org.apache.ibatis.datasource.DataSourceFactory;
10 import org.apache.ibatis.io.Resources;
11 import org.apache.ibatis.session.SqlSession;
12 import org.apache.ibatis.session.SqlSessionFactory;
13 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
14 
15 /**
16  * @author Siva
17  *
18  */
19 public class MyBatisSqlSessionFactory
20 {
21     private static SqlSessionFactory sqlSessionFactory;
22     
23     private static final Properties PROPERTIES = new Properties();
24     
25     static
26     {
27         try {
28             InputStream is = DataSourceFactory.class.getResourceAsStream("/application.properties");
29             PROPERTIES.load(is);
30         } catch (IOException e) {
31             e.printStackTrace();
32         }
33     }
34     
35     public static SqlSessionFactory getSqlSessionFactory()
36     {
37         if(sqlSessionFactory==null) 
38         {
39             InputStream inputStream = null;
40             try
41             {
42                 inputStream = Resources.getResourceAsStream("mybatis-config.xml");
43                 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
44             }catch (IOException e)
45             {
46                 throw new RuntimeException(e.getCause());
47             }finally {
48                 if(inputStream != null){
49                     try {
50                         inputStream.close();
51                     } catch (IOException e) {
52                     }
53                 }
54             }
55         }
56         return sqlSessionFactory;
57     }
58     
59     public static SqlSession getSqlSession() 
60     {
61         return getSqlSessionFactory().openSession();
62     }
63     
64     public static Connection getConnection() 
65     {
66         String driver = PROPERTIES.getProperty("jdbc.driverClassName");
67         String url = PROPERTIES.getProperty("jdbc.url");
68         String username = PROPERTIES.getProperty("jdbc.username");
69         String password = PROPERTIES.getProperty("jdbc.password");
70         Connection connection = null;
71         try {
72             Class.forName(driver);
73             connection = DriverManager.getConnection(url, username, password);
74         } catch (Exception e) {
75             throw new RuntimeException(e);
76         } 
77         return connection;
78     }
79 }

5.配置及资源文件

(1)mybatis-config.xml

 1 <?xml version="1.0" encoding="UTF-8" ?>
 2 <!DOCTYPE configuration
 3   PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 4   "http://mybatis.org/dtd/mybatis-3-config.dtd">
 5 <configuration>
 6 
 7       <properties resource="application.properties"/>
 8         
 9       <typeAliases>
10           <package name="com.mybatis3.domain"/>
11       </typeAliases>
12     
13       <environments default="development">
14         <environment id="development">
15           <transactionManager type="JDBC"/>
16           <dataSource type="POOLED">
17             <property name="driver" value="${jdbc.driverClassName}"/>
18             <property name="url" value="${jdbc.url}"/>
19             <property name="username" value="${jdbc.username}"/>
20             <property name="password" value="${jdbc.password}"/>
21           </dataSource>
22         </environment>
23       </environments>
24       
25       <mappers>
26         <mapper resource="com/mybatis3/mappers/StudentMapper.xml"/>
27       </mappers>
28       
29       
30 </configuration>

(2)application.properties

1 ################### DataSource Configuration ##########################
2 
3 jdbc.driverClassName=com.mysql.jdbc.Driver
4 jdbc.url=jdbc:mysql://localhost:3306/elearning
5 jdbc.username=root
6 jdbc.password=1234
 1 CREATE TABLE ADDRESSES 
 2 (
 3   ADDR_ID INT(11) NOT NULL AUTO_INCREMENT,
 4   STREET VARCHAR(50) NOT NULL,
 5   CITY VARCHAR(50) NOT NULL,
 6   STATE VARCHAR(50) NOT NULL,
 7   ZIP VARCHAR(10) DEFAULT NULL,
 8   COUNTRY VARCHAR(50) NOT NULL,
 9   PRIMARY KEY (ADDR_ID)
10 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;
11 
12 CREATE TABLE STUDENTS 
13 (
14   STUD_ID INT(11) NOT NULL AUTO_INCREMENT,
15   NAME VARCHAR(50) NOT NULL,
16   EMAIL VARCHAR(50) NOT NULL,
17   PHONE VARCHAR(15) DEFAULT NULL,  
18   DOB DATE DEFAULT NULL,
19   BIO LONGTEXT DEFAULT NULL,
20   PIC BLOB DEFAULT NULL,
21   ADDR_ID INT(11) DEFAULT NULL,  
22   PRIMARY KEY (STUD_ID),
23   CONSTRAINT FK_STUDENTS_ADDR FOREIGN KEY (ADDR_ID) REFERENCES ADDRESSES (ADDR_ID)
24 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;
25 
26 CREATE TABLE TUTORS 
27 (
28   TUTOR_ID INT(11) NOT NULL AUTO_INCREMENT,
29   NAME VARCHAR(50) NOT NULL,
30   EMAIL VARCHAR(50) NOT NULL,
31   PHONE VARCHAR(15) DEFAULT NULL,  
32   DOB DATE DEFAULT NULL,
33   BIO LONGTEXT DEFAULT NULL,
34   PIC BLOB DEFAULT NULL,
35   ADDR_ID INT(11) DEFAULT NULL,
36   PRIMARY KEY (TUTOR_ID),
37   CONSTRAINT FK_TUTORS_ADDR FOREIGN KEY (ADDR_ID) REFERENCES ADDRESSES (ADDR_ID)  
38 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;
39 
40 
41 CREATE TABLE COURSES 
42 (
43   COURSE_ID INT(11) NOT NULL AUTO_INCREMENT,
44   NAME VARCHAR(100) NOT NULL,
45   DESCRIPTION VARCHAR(512) DEFAULT NULL,
46   START_DATE DATE DEFAULT NULL,
47   END_DATE DATE DEFAULT NULL,
48   TUTOR_ID INT(11) NOT NULL,
49   PRIMARY KEY (COURSE_ID),
50   CONSTRAINT FK_COURSE_TUTOR FOREIGN KEY (TUTOR_ID) REFERENCES TUTORS (TUTOR_ID)
51 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;
52 
53 
54 CREATE TABLE COURSE_ENROLLMENT
55 (
56   COURSE_ID INT(11) NOT NULL,
57   STUD_ID INT(11) NOT NULL,
58   PRIMARY KEY (COURSE_ID,STUD_ID),
59   CONSTRAINT FK_ENROLLMENT_STUD FOREIGN KEY (STUD_ID) REFERENCES STUDENTS (STUD_ID),
60   CONSTRAINT FK_ENROLLMENT_COURSE FOREIGN KEY (COURSE_ID) REFERENCES COURSES (COURSE_ID)
61 ) ENGINE=INNODB DEFAULT CHARSET=LATIN1;
62 
63 
64 
65 CREATE TABLE USER_PICS 
66 (
67   ID INT(11) NOT NULL AUTO_INCREMENT,
68   NAME VARCHAR(50) DEFAULT NULL,
69   PIC BLOB,
70   BIO LONGTEXT,
71   PRIMARY KEY (ID)
72 ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=LATIN1;
1 DROP TABLE IF EXISTS USER_PICS;
2 DROP TABLE IF EXISTS COURSE_ENROLLMENT;
3 DROP TABLE IF EXISTS COURSES;
4 DROP TABLE IF EXISTS TUTORS;
5 DROP TABLE IF EXISTS STUDENTS;
6 DROP TABLE IF EXISTS ADDRESSES;
 1 --Sample data for table ADDRESSES
 2 
 3 INSERT INTO ADDRESSES (ADDR_ID,STREET,CITY,STATE,ZIP,COUNTRY) VALUES 
 4  (1,'4891 Pacific Hwy','San Diego','CA','92110','San Diego'),
 5  (2,'2400 N Jefferson St','Perry','FL','32347','Taylor'),
 6  (3,'710 N Cable Rd','Lima','OH','45825','Allen'),
 7  (4,'5108 W Gore Blvd','Lawton','OK','32365','Comanche');
 8 
 9 -- Sample data for table STUDENTS
10 
11 INSERT INTO STUDENTS (STUD_ID,NAME,EMAIL,PHONE,DOB,BIO,PIC,ADDR_ID) VALUES 
12  (1,'Timothy','timothy@gmail.com','123-123-1234','1988-04-25',NULL,NULL,3),
13  (2,'Douglas','douglas@gmail.com','789-456-1234','1990-08-15',NULL,NULL,4);
14 
15 -- Sample data for table TUTORS
16 
17 INSERT INTO TUTORS (TUTOR_ID,NAME,EMAIL,PHONE,DOB,BIO,PIC,ADDR_ID) VALUES 
18  (1,'John','john@gmail.com','111-222-3333','1980-05-20',NULL,NULL,1),
19  (2,'Paul','paul@gmail.com','123-321-4444','1981-03-15',NULL,NULL,2);
20 
21 -- Sample data for table courses
22 
23 INSERT INTO COURSES (COURSE_ID,NAME,DESCRIPTION,START_DATE,END_DATE,TUTOR_ID) VALUES 
24  (1,'Quickstart Core Java','Core Java Programming','2013-03-01','2013-04-15',1),
25  (2,'Quickstart JavaEE6','Enterprise App Development using JavaEE6','2013-04-01','2013-08-30',1),
26  (3,'MyBatis3 Premier','MyBatis 3 framework','2013-06-01','2013-07-15',2);
27 
28 -- Sample data for table COURSE_ENROLLMENT
29 
30 INSERT INTO COURSE_ENROLLMENT (COURSE_ID,STUD_ID) VALUES 
31  (1,1),
32  (1,2),
33  (2,2);
1 log4j.rootLogger=INFO, stdout
2 
3 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
4 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
5 log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %c - %m%n
6 
7 log4j.logger.com.mybatis3=DEBUG

6.测试文件

 1 package com.mybatis3.services;
 2 
 3 import java.util.Date;
 4 import java.util.List;
 5 
 6 import org.junit.AfterClass;
 7 import static org.junit.Assert.*;
 8 import org.junit.BeforeClass;
 9 import org.junit.Test;
10 
11 import com.mybatis3.domain.Student;
12 
13 
14 
15 /**
16  * @author Siva
17  *
18  */
19 public class StudentServiceTest 
20 {
21     private static StudentService studentService;
22     
23     @BeforeClass
24     public static void setup()
25     {
26         studentService = new StudentService();
27         TestDataPopulator.initDatabase();
28     }
29     @AfterClass
30     public static void teardown()
31     {
32         studentService = null;
33     }
34     
35     @Test
36     public void testFindAllStudents() 
37     {
38         List<Student> students = studentService.findAllStudents();
39         assertNotNull(students);
40         for (Student student : students)
41         {
42             assertNotNull(student);
43             System.out.println(student);
44         }
45         
46     }
47     
48     @Test
49     public void testFindStudentById() 
50     {
51         Student student = studentService.findStudentById(1);
52         assertNotNull(student);
53     }
54     
55     @Test
56     public void testCreateUStudent() 
57     {
58         Student student = new Student();
59         int id = 4;
60         student.setStudId(id);
61         student.setName("student_"+id);
62         student.setEmail("student_"+id+"gmail.com");
63         student.setDob(new Date());
64         studentService.createStudent(student);
65         Student newStudent = studentService.findStudentById(id);
66         assertNotNull(newStudent);
67         assertEquals("student_"+id, newStudent.getName());
68         assertEquals("student_"+id+"gmail.com", newStudent.getEmail());
69     }
70     
71     @Test    
72     public void testUpdateStudent() 
73     {
74         int id = 2;
75         Student student =studentService.findStudentById(id);
76         student.setStudId(id);
77         student.setName("student_"+id);
78         student.setEmail("student_"+id+"gmail.com");
79         Date now = new Date();
80         student.setDob(now);
81         studentService.updateStudent(student);
82         Student updatedStudent = studentService.findStudentById(id);
83         assertNotNull(updatedStudent);
84         assertEquals("student_"+id, updatedStudent.getName());
85         assertEquals("student_"+id+"gmail.com", updatedStudent.getEmail());
86         
87     }
88 }
 1 /**
 2  * 
 3  */
 4 package com.mybatis3.services;
 5 
 6 import java.io.Reader;
 7 import java.sql.Connection;
 8 
 9 import org.apache.ibatis.io.Resources;
10 import org.apache.ibatis.jdbc.ScriptRunner;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13 
14 import com.mybatis3.util.MyBatisSqlSessionFactory;
15 
16 /**
17  * @author Siva
18  *
19  */
20 public class TestDataPopulator 
21 {
22     private static Logger logger = LoggerFactory.getLogger(TestDataPopulator.class);
23     
24     public static void main(String[] args) {
25         initDatabase();
26     }
27     
28     public static void initDatabase()
29     {
30         Connection connection = null;
31         Reader reader = null;
32         try {
33             connection = MyBatisSqlSessionFactory.getConnection();
34             ScriptRunner scriptRunner = new ScriptRunner(connection);
35             reader = Resources.getResourceAsReader("sql/drop_tables.sql");
36             scriptRunner.runScript(reader);
37             logger.info("drop_tables.sql executed successfully");
38             reader = Resources.getResourceAsReader("sql/create_tables.sql");
39             scriptRunner.runScript(reader );
40             logger.info("create_tables.sql executed successfully");
41             reader = Resources.getResourceAsReader("sql/sample_data.sql");
42             scriptRunner.runScript(reader );
43             logger.info("sample_data.sql executed successfully");
44             connection.commit();
45             reader.close();
46             scriptRunner.closeConnection();
47         } catch (Exception e) {
48             throw new RuntimeException(e);
49         }
50         
51     }
52 }
原文地址:https://www.cnblogs.com/shamgod/p/5439477.html