Spring学习笔记之使用对象-关系映射持久化数据
来源:互联网 发布:淘宝退款在哪里看 编辑:程序博客网 时间:2024/05/16 18:30
ORM(object-relational mapping)——对象/关系 映射。
Spring对多个ORM框架提供了支持。下面分别介绍Spring对Hibernate和JPA(Java持久化API,java Persistence API)的支持。
maven:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>4.3.8.RELEASE</version> </dependency>
1.在Spring中集成Hibernate
1.1声明Hibernate的Session工厂
使用Hibernate所需的主要接口是org.hibernate.Session。Session接口提供了基本的数据访问功能。获取Hibernate Session对象的标准方式是借助于Hibernate Session Factory接口的实现类。SessionFactory主要负责Hibernate Session的打开、关闭以及管理。
从3.1版本开始,Spring提供了三个Session工厂bean供我们选择:
- org.springframework.orm.hibernate3.LocalSessionFactoryBean
- org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
- org.springframework.orm.hibernate4.LocalSessionFactoryBean
至于选择哪一个Session工厂,取决于使用哪一个版本的Hibernate以及使用XML还是使用注解定义对象-数据库之间的映射关系。XML——LocalSessionFactoryBean
,注解——AnnotationSessionFactoryBean。
org.springframework.orm.hibernate4.LocalSessionFactoryBean
类似于前俩个的结合体,能够支持基于XML的映射和基于注解的映射。
/** * org.springframework.orm.hibernate3.LocalSessionFactoryBean * @param dataSource * @return */ @Bean @Autowired public LocalSessionFactoryBean sessionFactory(DataSource dataSource){ LocalSessionFactoryBean sfb = new LocalSessionFactoryBean(); sfb.setDataSource(dataSource); sfb.setMappingResources(new String[] {"test.hnm.xml"});//设置Hibernate映射文件 Properties prop = new Properties(); prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect"); sfb.setHibernateProperties(prop); return sfb; } /** * org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean * @param dataSource * @return */ @Bean @Autowired public AnnotationSessionFactoryBean sessionFactory(DataSource dataSource){ AnnotationSessionFactoryBean bean = new AnnotationSessionFactoryBean(); bean.setDataSource(dataSource); bean.setPackagesToScan(new String[] {"com.sh.domin"}); Properties prop = new Properties(); prop.setProperty("dialect", "org.hibernate.dialect.MySQLDialect"); bean.setHibernateProperties(prop); return bean; }/** * org.springframework.orm.hibernate4.LocalSessionFactoryBean * 基于注解的方式 * @param dataSource * @return */ @Bean @Autowired public LocalSessionFactoryBean sessionFactory(DataSource dataSource){ LocalSessionFactoryBean sfb = new LocalSessionFactoryBean(); sfb.setDataSource(dataSource); //使用PackagesToScan属性告诉Spring要扫描一个或多个包以查找域类,这些类通过主机的方式表明 //要使用Hibernate进行持久化,这些类可以使用的注解包括JPA的@Entity或@MappedSuperclass以及Hibernate的@Entity。 sfb.setPackagesToScan(new String[] {"com.sh.domin"}); Properties properties = new Properties(); properties.setProperty("dialect", "org.hibernate.dialect.MySQLDialect"); sfb.setHibernateProperties(properties); return sfb; }
1.2构建不依赖于Spring 的hibernate代码
使用上下文Session.
@Repositorypublic class HibernateRepository { @Autowired private SessionFactory sessionFactory;//注入SessionFactory public Session currentSession(){ return sessionFactory.getCurrentSession();//从SessionFactory众获取当前Session } @Transactional public List<User> list(){ //使用当前Session查询user表中的所有数据方法list集合中 return (List<User>)currentSession().createCriteria(User.class).list(); }}
实体User:
@Entity()@Table(name="user")public class User implements Serializable { private static final long serialVersionUID = -4869287368065710953L; @Id private int id; private String username; private String password; private int sex; @Column(name="id",nullable=false) public int getId() { return id; } public void setId(int id) { this.id = id; } @Column(name="username") public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Column(name="password") public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Column(name="sex") public int getSex() { return sex; } public void setSex(int sex) { this.sex = sex; }}
@Repository是Spring的另一种构造性注解,它能够像其他注解一样被Spring的组件扫描所扫描到,只要这个Repository类在组件扫描所涵盖的包中即可。它还有一项任务就是捕获平台相关的异常,然后使用Spring统一非检查型异常的形式重新抛出。因此,我们需要在Spring应用上下文中添加一个PersistenceExceptionTranslationPostProcessor Bean:
@Bean public BeanPostProcessor persistenceTranslation(){ return new PersistenceExceptionTranslationPostProcessor(); }
这是一个Bean后置处理器,它会在所有拥有#Repository注解的类上添加一个通知器,这样就会捕获任何平台相关的异常并以Spring非检查型访问异常的形式重新抛出。
1.3 遇见的异常
org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
原因:Hibernate4 No Session found for current thread原因
此demo的解决办法:
1.启用TransactionManagement:
@Configuration@ComponentScan(basePackages={"com.sh.*"},excludeFilters={ @Filter(type=FilterType.ANNOTATION, value=EnableWebMvc.class)})@EnableTransactionManagementpublic class RootConfig {...}
2.配置Bean:
@Bean @Autowired public HibernateTransactionManager transactionManager(SessionFactory sessionFactory){ HibernateTransactionManager transactionManager = new HibernateTransactionManager(); transactionManager.setSessionFactory(sessionFactory); return transactionManager; }
3.访问持久层时加注解@Transactional
@Transactional public List<User> list(){ //使用当前Session查询user表中的所有数据方法list集合中 return (List<User>)currentSession().createCriteria(User.class).list(); }
Connection was closed in SingleConnectionDataSource
解决办法:设置SuppressClose为true
/** * 配置数据源 * @return */ @Bean public DataSource dataSource(){ SingleConnectionDataSource dataSource = new SingleConnectionDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/ceshi"); dataSource.setUsername("root"); dataSource.setPassword("root"); dataSource.setSuppressClose(true); return dataSource; }
org.hibernate.AnnotationException: No identifier specified for entity异常
原因:在使用Hibernate的映射表的时候Entity类是必须要有主键的,否则就会报这个异常。
解决办法:
主键上加上: @Id
@Column(name = “id”, unique = false, nullable = false)
例如上面User类写的那样。
demo源码:SpringMVC4+Hibernate4 Demo
2 Spring与Java持久化API
Java持久化API(Java Persistence API,JPA)诞生在EJB2实体Bean的废墟之上,并成为下一代Java持久化标准。JPA是基于POJO的持久化机制,它从Hibernate和Java数据对象(Java Data Object,JDO)上借鉴了很多理念并加入了Java5注解的特性。
2.1配置实体管理器工厂
在Spring中使用JPA的第一步是要在Spring应用上下文中将实体类管理器工厂(entity manager factory)按照bean的形式进行配置。
基于JPA的应用程序需要使用EntityManagerFactory的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:
- 应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事物中对其进行控制。这种方式的实体管理器适合于不运行在Java EE容器的独立应用程序。
- 容器管理类型(Container-managed):实体管理器由Java EE创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或JNDI来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适合与Java EE容器,在这种情况下会希望在persistence.xml指定的JPA配置之外保持一些自己对JPA的控制。
对于使用JPA的Spring开发者来说,不管你使用哪种EntityManagerFactory。Spring都负责管理EntityManager。对于应用程序管理类型的实体管理器,Spring承担了应用程序的角色并以透明的方式处理EntityManager。在容器管理的场景下,Spring会担当容器的角色。
这两种实体管理器工厂分别有对应的Spring工厂Bean创建:
- LocalEntityManagerFactoryBean生成应用程序管理类型的EntityManagerFactory
- LocalContainerEntityManagerFactoryBean生成容器管理类型的EntityManagerFactory。
配置应用程序管理类型的JPA:
对于应用程序管理类型的实体管理器工厂来说,它绝大部分配置信息来源于一个名为persistence.xml的配置文件。这个文件必须位于类路径下的META-INF目录下。
persistence.xml的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类。简单来讲,persistence.xml列出了一个或多个的持久化类以及一些其他的配置如数据源和基于XML的配置文件。
persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" xmlns:persistence="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd "> <!-- Name属性用于定义持久化单元的名字 (name必选,空值也合法); transaction-type 指定事务类型(可选) --> <persistence-unit name="unitName" transaction-type="JTA"> <!-- 描述信息.(可选) --> <description> </description> <!-- javax.persistence.PersistenceProvider接口的一个实现类(可选) --> <provider> </provider> <!-- Jta-data-source和 non-jta-data-source用于分别指定持久化提供商使用的JTA和/或non-JTA数据源的全局JNDI名称(可选) --> <jta-data-source>java:/MySqlDS</jta-data-source> <non-jta-data-source> </non-jta-data-source> <!-- 声明orm.xml所在位置.(可选) --> <mapping-file>product.xml</mapping-file> <!-- 以包含persistence.xml的jar文件为基准的相对路径,添加额外的jar文件.(可选) --> <jar-file>../lib/model.jar</jar-file> <!-- 显式列出实体类,在Java SE 环境中应该显式列出.(可选) --> <class>com.domain.User</class> <class>com.domain.Product</class> <!-- 声明是否扫描jar文件中标注了@Enity类加入到上下文.若不扫描,则如下:(可选) --> <exclude-unlisted-classes/> <!-- 厂商专有属性(可选) --> <properties> <!-- hibernate.hbm2ddl.auto= create-drop / create / update --> <property name="hibernate.hbm2ddl.auto" value="update" /> <property name="hibernate.show_sql" value="true" /> </properties> </persistence-unit> </persistence>
Bean:
/** * 配置应用程序管理类型的JPA * @return */ @Bean public LocalEntityManagerFactoryBean entityManagerFactoryBean(){ LocalEntityManagerFactoryBean emfb = new LocalEntityManagerFactoryBean(); emfb.setPersistenceUnitName("unitName"); return emfb; }
使用容器管理类型的JPA
Bean:
/** * 配置容器管理类型的JPA */ @Bean @Autowired public LocalContainerEntityManagerFactoryBean containerEntityManagerFactoryBean(DataSource dataSource,JpaVendorAdapter jpaVendorAdapter){ LocalContainerEntityManagerFactoryBean lcemf = new LocalContainerEntityManagerFactoryBean(); lcemf.setDataSource(dataSource); //指定使用哪一个厂商的JPA实现 lcemf.setJpaVendorAdapter(jpaVendorAdapter); //扫描com.jpa.domin包下带有@Entity注解的实体类 lcemf.setPackagesToScan("com.jpa.domin"); return lcemf; }
jpaVendorAdapter属性用于指明所使用的是哪一个厂商的JPA实现。Spring提供了多个JPA厂商适配器:
- EclipseLinkJpaVendorAdapter
- HibernateJpaVendorAdapter
- OpenJpaVendorAdapter
- TopLinkJpaVendorAdapter(在spring3.1的版本中已经将其废弃)
这里使用Hibernate作为JPA实现
/** * 指明使用哪一个厂商的JPA实现 * @return */ @Bean public JpaVendorAdapter jpaVendorAdapter(){ HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter(); //设置数据库 adapter.setDatabase(Database.MYSQL); adapter.setShowSql(true); adapter.setGenerateDdl(false); //设置数据库方言 adapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect"); return adapter; }
database属性指定的使用的数据库。
Hibernate的JPA适配器支持多种数据库,可以通过database属性配置使用哪一个数据库:
2.2编写基于JPA的Repository
@Repository@Transactionalpublic class TestRepository { @PersistenceUnit private EntityManagerFactory emf; public User findById(int id){ return this.emf.createEntityManager().find(User.class, id); }}
@PersistenceUnit注解会将EntityManagerFactory 注入到Repository中。
这里麻烦的是每个方法都会调用this.emf.createEntityManager().来创建EntityManager。因为EntityManager并不是线程安全的,所以我们不能预先准备好它。但是我们可以借助@PersistenceContext注解为Repository设置EntityManager:
@Repository@Transactionalpublic class JpaRepository { @PersistenceContext private EntityManager em; public User getUserById(int id){ return em.find(User.class, id); }}
真相是@PersistenceContext并不会真正注入EntityManager——精确来讲是这样的。他没有将真正的EntityManager设置给Repository,而是给了他一个EntityManager的代理。真正的EntityManager是与当前事务相关联的哪一个,如果不存在这样的EntityManager的话,就会创建一个新的。
@Transactional表明这Repository中的持久化方法是在事务上下文中执行。
最后,因为我们没有使用spring 模板,所有要配置异常转换的bean给@Repository注解:
@Bean public BeanPostProcessor persistenceTranslation(){ return new PersistenceExceptionTranslationPostProcessor(); }
不过,不论是JPA还是Hibernate,这个是可以省略的,如果你希望在Repository中抛出特定的JPA或Hibernate异常,只需将PersistenceExceptionTranslationPostProcessor省略掉即可,这样原来的异常就会正常的处理。不会转换成spring的异常体系。
本节demo:Spring MVC+JPA Demo
3.借助Spring Data实现自动化的JPA Repository
Spring Data 能够让我们之编写Repository接口就可以了,而不需要实现类。
public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>{}
编写Spring Data JPA Repository的关键在于要从一组接口中挑选一个进行扩展。这里我们扩展了Spring data JPA的JpaRepository。后面会介绍其他几个接口。通过这种方式,JpaRepository进行了参数化,所有它就能知道这是一个用来持久化User对象的Repository,并且User的ID类型为Integer。另外它还会继承18个执行持久化的通用方法。
我们不需要创建ISpringDataJpaRepository 的实现,它的实现是由Spring Data完成的。为了让Spring Data 创建ISpringDataJpaRepository 的实现,我们需要在Spring配置中添加一个元素:
XML配置:
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd" default-lazy-init="true"> <description>SpringJpa配置</description> ... <!-- 重要配置:启用扫描并自动创建代理的功能 --> <jpa:repositories base-package="com.jpa.dao" transaction-manager-ref="transactionManager" entity-manager-factory-ref="entityManagerFactory"/> <!-- Jpa 事务管理器 --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> ...</bean>
java配置:
@EnableJpaRepositories(basePackages="com.jpa.dao")public class RootConfig { ... /** * JPA事物管理器 * @param emf * @return */ @Bean @Autowired public JpaTransactionManager transactionManager(EntityManagerFactory emf){ JpaTransactionManager jtm = new JpaTransactionManager(emf); return jtm; }}
这里我们指定了一个基础包,<jpa:repositories>
会扫描它的基础包来查找扩展自Spring Data JPA Repository接口的所有接口。如果发现了扩展自Repository的接口,它会自动生成(在应用启动的时候)这个接口的实现。
配置好了这些以后就可以使用了,我们之前说过,现在的这个接口已经继承了18个方法,所以我们用其中的一个findone():
@Controllerpublic class TestController { @Autowired private ISpringDataJpaRepository sdjr; @RequestMapping("findone") public String findOne(){ User user = this.sdjr.findOne(1); System.out.println(user.getUsername()); return "index"; }}
3.1定义查询方法
public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>{ User findByUsername(String username);}
当创建Repository实现的时候,Spring Data会检查Repository接口的所有方法,解析方法的名称,并基于被持久化的对象来试图推测方法的目的。本质上,Spring Data定义了一组小型的领域特定语言(domin-specific language,DSL),在这里,持久化的细节都是通过Repository方法的签名来描述的。
Repository方法是一个由动词、一个可选的主题(subject),关键词By以及一个断言所组成的。在上面那个方法中,动词是find,断言是Username,主题并没有指定,暗含的主题是User。
又比如这个:readUserByFirstnameOrLastname()。
Spring Data 允许在方法名中使用四种动词:get,read,find和count,其中,动词get,read和find是同一的,这三个动词对应的Repository方法都会查询数据并返回对象。而动词count则会返回匹配对象的数量,而不是对象本身。
Repository方法的主题是可选的。它的主要目的是让你在命名方法的时候,有更多的灵活性。
要查询的对象类型是通过如何参数化JpaRepository接口来确定的,而不是方法名称中的主题。
在省略主题的时候,有一种例外情况。如果主题的名称以Distinct开头的话,那么在生成查询的时候会确保所返回结果集中不包含重复记录。断言是方法名称中最为有意思的部分,它指定了限制结果集的属性。
在断言中,会有一个或多个限制结果的条件。每个条件必须引用一个属性,并且还可以指定一种比较操作。如果省略比较操作符的话,那么这暗指是一种相等比较操作。不过,我们也可以选择其他的比较操作,包括如下的种类:
- IsAfter,After,IsGreaterThan,GreaterThan
- IsGreaterThanEQual,GreaterThanEqual
- IsBefore,Before,IsLessThan,LessThan
- IsLessThanEqual,LessThanEqual
- IdBetween,Between
- IdNull,Null
- IsNotNull,NotNull
- IsIn,In
- IsNotIn,NotIn
- IsStartingWith,StartingWith,StartsWith
- IsEndingWith,EndingWith,EndsWith
- IsContaining,Containing,Contains
- IsLike,Like
- IsNotLike,NotLike
- IsTrue,True
- IsFalse,False
- Is,Equals
- IsNot,Not
要处理String类型的属性时,条件中可能还会包含IgnoringCase或IgnoresCase,这样在执行对比的时候就会不在考虑字符是大写还是小写。
例如:
List<User> readByFirstnameIgnoringCaseOrLastnameIgnoringCase(String first, String last);
或者这样,结果是一样的:
List<User> readByFirstnameOrLastnameAllIgnoringCase(String first, String last);
排序:
List<User> readByFirstnameOrLastnameOrderByLastnameAscFirstnameDesc(String first,String last);
可以看到条件部分可以通过And或者Or进行分割。
如下给出了几个符合方法命名约定的方法签名:
List<Pet> findPetsByBreedIn(List<String> breed)
int countProductsByDisContinuedTrue()
List<Order> findByShippingDateBetween(Date start, Date end)
3.2声明自定义查询
如果所需的数据无法通过方法名称进行恰当的描述,那么我们可以使用@Query注解,为Spring Data提供要执行的查询。
//这里的from User指的是User对象,换成user则无法运行 @Query("select u from User u where u.email like '%qq.com'") List<User> findAllQQEmailUser();
3.3混合自定义的功能
当Spring Data JPA为Repository接口生成实现的时候,它还会查找名字与接口相同,并且添加了Impl后缀的一个类。如果这个类存在的话,Spring Data JPA将会把它的方法与Spring Data JPA所生成的方法合并在一起。对于我们的接口ISpringDataJpaRepository来说,要查找ISpringDataJpaRepositoryImpl类。
所以,在使用Spring Data JPA的同时,我们也可以使用原始的EntityManager。
这是我们的ISpringDataJpaRepositoryImpl类:
@Transactionalpublic class ISpringDataJpaRepositoryImpl implements UserSweeper{ @PersistenceContext private EntityManager em; public int sexSweep() { String update = "UPDATE User user set user.sex = 1 WHERE user.sex = 0"; return em.createQuery(update).executeUpdate(); }}
这里,我们没有实现ISpringDataJpaRepository。ISpringDataJpaRepository将由Spring Data JPA实现。ISpringDataJpaRepositoryImpl实现了另一个接口UserSweeper:
public interface UserSweeper { int sexSweep();}
最后,让ISpringDataJpaRepository继承一下UserSweeper,或者只要确保UserSweeper中的方法在ISpringDataJpaRepository中出现,就能把ISpringDataJpaRepositoryImpl中的方法和Spring Data JPA为ISpringDataJpaRepository生成的方法绑定在一起。然后通过ISpringDataJpaRepository来使用它:
public interface ISpringDataJpaRepository extends JpaRepository<User, Integer>,UserSweeper{...}
使用:
@Controllerpublic class TestController { @Autowired private ISpringDataJpaRepository sdjr; @RequestMapping("sex") public String countSex(){ int count = this.sdjr.sexSweep(); System.out.println(count); return "index"; }}
如前所述,Spring Data JPA将实现类与接口关联起来是基于接口的名称。但是,Impl后缀只是默认的做法,如果你想使用其他后缀的话,只需要在配置@EnableJpaRepositories的时候设置repositoryImplementationPostfix属性即可。下面把后缀改为Helper:
java配置:
@EnableJpaRepositories(basePackages="com.jpa.dao", repositoryImplementationPostfix="Helper")
XML配置:
<jpa:repositories base-packages="com.jpa.dao" repository-impl-postfix="Helper" />
本节Demo:Spring Data JPA Demo
- Spring学习笔记之使用对象-关系映射持久化数据
- spring(11)使用对象-关系映射持久化数据
- 第11章-使用对象-关系映射持久化数据
- 学习笔记之什么是持久化和对象关系映射ORM技术
- 学习笔记之什么是持久化和对象关系映射ORM技术
- 学习笔记之什么是持久化和对象关系映射ORM技术
- 学习笔记之什么是持久化和对象关系映射ORM技术
- Hibernate学习之 -- 使用Middlegen-Hibernate-r5创建oracle10g的table的hibernate映射文件,Hibernate学习笔记三 ---持久化类和关系数据
- 《数据访问模式》笔记:对象/关系映射
- Hibernate 学习笔记05 --对象关系映射
- 什么是持久化和对象关系映射ORM技术
- 持久化和对象关系映射ORM技术
- 什么是持久化和对象关系映射ORM技术
- 什么是持久化和对象关系映射ORM技术
- 什么是持久化和对象关系映射ORM技术
- 什么是持久化和对象关系映射ORM技术
- Hibernate知识整理4---关系映射及持久化对象
- Hibernate学习笔记 之 持久化对象的状态
- 蓝桥杯=横向打印二叉树
- JS设计模式之基于组合模式的code review
- DFS+剪枝
- CC3200 freemodbus-tcp移植过程
- mfc的控件与响应顺序
- Spring学习笔记之使用对象-关系映射持久化数据
- VS2015 使用GDI+ 和 easyX的安装
- 2017年华东师范大学网络赛 A
- KMP算法 Java实现
- company——桶思想
- 模块化开发
- JAVA String、StringBuilder和StringBuffer区别
- 树莓派笔记(二)--“闪烁灯”
- windows程序设计(二)