Онлайн приложение |
Автор: Administrator |
11.09.2011 22:16 |
Ничего нового возможно я не расскажу, но подъитожу =) Вот вы создаете какое-либо приложение на unity3d или может даже flash, неважно что это - игра или библиотека скажем и вам уже мало простого хождения по статичным сценам, и в один прекрасный день вы себе говорите "Хочу чтобы мое приложение было онлайн и чтобы пользователи могли сохранять свой инвентарь после выхода из игры или получать уведомления о новых журналах и книгах в библиотеке". Но одним "Хочу" тут не отделаешься, нужно работать с базой данных и как вы могли заметить уже, для сервер решения мы будем использовать smartfox и базу MySql (можно изменить на любую другую, поменяв драйвера коннекторов и настройки- PostSql. Microsoft Sql и тд). Как же мы будем записывать наши данные в бд? Прямыми Sql- запросами из наших extension для smartfox? Это не приемлемо, не только из-за возможности ошибиться в огромных запросах и загромождении кода расширения, но и в целях безопасности. На помощь нам прийдет ORM, а конкретно Hibernate, с дополнительными библиотеками типа jpa, spring fraemwork, которые помогают сконфигурировать логику записи в бд. Бум, Туториал по Hibernate на русском:
1. Понятие ORM
2. Hibernate. Общие сведения
3. Downloads
4. Главный конфигурационный файл (hibernate.cfg.xml)
5. Простейший класс
6. HibernateUtil
7. Запускаем первый пример
8. Hibernate и его кеш.
1. Понятие ORM
ORM(Object/Relational Mapping) - это способ сохранения объектов в реляционную базу данных. Другими словами ORM освобождает нас от работы с SQL и позволяет сосредоточиться на ООП. Основой ORM есть конфигурационный файл, в котором описывается как объект будет записываться в БД. Если с примитивными полями все так же примитивно: строковое поле записывается в колонку с типом varchar, целочисленное - в int, то со связями между объектами все интересней. Например, у существуют классы Книга и Автор, для обоих созданы конфигурационные файлы, в которых описаны их поля. Но Книга, кроме всего прочего, имеет ссылку на Автора. В таком случае таблицы в БД будут связаны по внешнему ключу, что так же описывается в одном из конфигурационных файлов.
Примечание: ORM может описываться не только в конфигурационных файлах, так же применяются аннотации, но для простоты понимания пока о них речь идти не будет.
2. Hibernate. Общие сведения
Hibernate - это один из самых популярных на сегодняшний день ORM-фреймворков. Конечно, то, что мы избавились от SQL - это плюс, но не нужно забывать и об отрицательных сторонах, главная из которых - производительность. Само собой, что работа с БД посредством грамотно написанного SQL-кода будет более производительной, нежели с использованием Hibernate. Однако SQL нужно еще уметь грамотно написать, в то время, как Hibernate создает запросы оптимизированные. Так же у разработчиков уходит уйма времени на написание запросов и хранимых процедур, работа же с Hibernate ускоряет процесс разработки.
Одной из привлекательных сторон Hibernate есть то, что он поддерживает диалекты ко всем популярным СУБД. Наши приложения становятся еще и СУБД-независимыми, т.к. единственное, что придется поменять если мы хотим перейти на другую СУБД, - это диалект в конфигурации Hibernate.
Для функционирования Hibernate нужны:
1. Главный конфигурационный файл, в котором описываются параметры соединения к БД.
2. Описание связи между классом и таблицей. Включает в себя связь между полем класса и колонкой таблицы. Также здесь описывается связь классов друг с другом.
3. Downloads
Давайте приступим к закачке Hibernate 3.2.6.ga. В будущем можно будет конечно качать более новую версию, но пока эта самая актуальная. Многие(и я в том числе) обожглись на том, что качали укомплектованный пакет Hibernate c оф. сайта. Но, как оказалось, библиотеки, которые идут в этом комплекте, имеют несовместимые версии, что приводит обычно к ClassNotFoundException при запуске приложения. Так вот, здесь собраны нужные библиотеки, которые можно скачать с центрального maven-репозитория:
hibernate-3.2.6.ga.jar
mysql-connector-java-5.1.6.jar log4j-1.2.9.jar dom4j-1.6.1.jar slf4j-api-1.5.2.jar slf4j-simple-1.5.2.jar jta-1.1.jar icu4j-2.6.1.jar commons-logging-1.0.4.jar commons-collections-3.1.jar cglib-nodep-2.1_3.jar
Все их нужно будет подключать к будущим Hibernate-проектам.
4. Главный конфигурационный файл(hibernate.cfg.xml)
Как было выше сказано, для функционирования Hibernate требует главного конфигурационного файла. Вот и он:
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url"> jdbc:mysql://localhost/mysql </property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <property name="hibernate.connection.autocommit">true</property> <property name="show_sql">true</property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.hbm2ddl.auto">update</property> <!-- Mapping files --> <mapping resource="Book.hbm.xml"/> </session-factory> </hibernate-configuration>
hibernate.connection.driver_class - указываем класс драйвера
show_sql - если установим в true, в консоль будут выводиться SQL-запросы, которые скрыты за Hibernate-кодом
dialect - диалект общения с БД
hibernate.hbm2ddl.auto - свойство, которое указывается что нужно сделать со схемой БД при инициализации. Может принимать такие значения:
update - сверяет схему БД с имеющимися конфигурациями классов, если мы внесли какие-то изменения, они автоматически занесуться в БД. При этом данные, которые были занесены в базу не изменятся - даже, если мы решили удалить некоторые поля из таблицы, они все одно останутся
create - каждый раз при запуске приложения, схема БД будет создаваться наново. Все данные, которые были занесены раньше, будут удалены
create-drop - каждый раз при запуске приложения, схема БД будет создаваться наново, а при завершении - удаляться. Все данные, которые были занесены во время работы приложения, будут удалены по завершению приложения
validate - при инициализации будет совершена проверка соответствуют ли конфигурации классов и схема БД. Если мы внесли изменение в конфигурацию какого-то класса, а схема в БД не была изменена, выбросится исключение
Далее идет раздел описания классов. Тут мы указываем файлы, которые отвечают за конфигурирование каждого из классов.
5. Простейший класс
Напишем простейший класс с тремя полями: идентификатор типа Long, строковое поле - название книги и количество страниц, представленное целочисленным типом. Обращаю внимание, что у класса обязан быть конструктор по умолчанию. Т.к. мы не объявили ни одного, то он и так будет.
package ru.javatalks.faq.persistence.hibernate.bookstore; /** * 10.07.2009 23:43:14 * * @author ctapobep */ public class Book { private Long id; private String title; private int pageCount; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public int getPageCount() { return pageCount; } public void setPageCount(int pageCount) { this.pageCount = pageCount; } }
Теперь опишем его конфигурационный файл. Все эти файлы должны иметь имя ClassName.hbm.xml. То есть в нашем случае это Book.hbm.xml:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="ru.javatalks.faq.persistence.hibernate.bookstore"> <class name="Book" table="books"> <id name="id" column="id" unsaved-value="null"> <generator class="native"/> </id> <property name="title" type="string" column="title" length="255"/> <property name="pageCount"/> </class> </hibernate-mapping>
По пунктам:
Код:
<property name="title" type="string" column="title" length="255"/>
Здесь мы установили, что в таблице создастся поле title с типом varchar(или varchar2 в Oracle), с длинной 255. Если б мы не утсановили длину, Hibernate определил бы ее сам, и составляла бы она максимальную допустимую длину данного типа для данной БД. Т.к. у нас MySQL, то длина поля и была бы 255. Что касается типа поля, то если его не указывать, Hibernate его определит сам, но только для примитивов и классов-оберток(Integer, Double etc.).
<property name="pageCount"/>
Как видите, здесь мы не указывали ни тип, ни название колонки - Hibernate все установит самостоятельно.
<id name="id" column="id" unsaved-value="null"> <generator class="native"/> </id>
Поле идентификатора обязательное условие для работы Hibernate. Его тип может быть и строкой, и числом, главное условие - уникальность. Атрибут unsaved-value определяет как Hibernate будет отличать уже сохраненный объект(persistent) от еще нетронутого(unsaved). В данном случае, если идентификатор объекта окажется null, значит он еще не был сохранен в базу. Кстати, здесь можем наблюдать выигрышь типа Long перед long: последний не может быть null, unsaved-value у него будет 0, что не так бросается в очи.
generator - это генератор идентификаторов. Его класс будет отвечать за то, как будет генерироваться уникальный идентификатор. В данном случае установлен класс native, что позволяет использовать генератор самой БД.
Подробней об идентификаторах и генераторах можно прочитать здесь.
<class name="Book" table="books">
Тег, в котором описывается класс. Здесь мы указали имя класса и таблицу, которая будет ему соответствовать. Подробней.
<hibernate-mapping package="ru.javatalks.faq.persistence.hibernate.bookstore">
Главный тег данного конфигурационного файла. Здесь мы указали лишь пакет, в котором хранится наш класс. Если бы мы здесь упоминали больше классов, не указывая их пакеты, то использовался именно этот. Хотелось бы заметить, что если б у нас был класс в дочернем пакете относительно описанного выше, то к этому классу пришлось бы обращаться, указывая абсолютное имя пакета, а не относительное. То бишь, допустим у нас есть пакет ru.javatalks.faq.persistence.hibernate.bookstore.derived, тогда к классу в нем мы бы обращались ru.javatalks.faq.persistence.hibernate.bookstore.DerivedClass.
6. HibernateUtil
Допишем класс-утилиту, которая будет загружать Hibernate и предоставлять к нему доступ:
HibernateUtil.java
package ru.javatalks.faq.persistence.hibernate.util; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /** * 26.07.2009 20:27:43 * * @author ctapobep */ public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { //creates the session factory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (ExceptionInInitializerError ex) { System.err.println("Initial SessionFactory creation failed: " + ex); throw new ExceptionInInitializerError(ex); } } /** * Gets hiberante session factory that was initialized at application startup. * * @return hibernate session factory */ public static SessionFactory getSessionFactory() { return sessionFactory; } }
Этот код создает объект SessionFactory - фабрика, с помощью которой создаются Hibernate-сессии, с которыми мы собственно и будем работать. Фабрика сессий - это потокобезопасный глобальный объект, который нужно инициализировать лишь один раз.
Очень важное место здесь играет объект Configuration и его метод configure(). Если инициализировать фабрику сессий таким образом, который описан выше, то файл hibernate.cfg.xml будет искаться в classpath. Мы также можем явно указать место нахождения этого файла, использовав перегруженный вариант метода configure(String).
7. Запускаем первый пример
Ну что, теории, наверное, достаточно. Нужно наконец-то запустить что-то
Давайте сохраним, извлечем, изменим и удалим запись из БД. Это типическая CRUD-операция(create, read, update, delete).
import org.hibernate.Session; import ru.javatalks.faq.persistence.hibernate.bookstore.Book; import ru.javatalks.faq.persistence.hibernate.util.HibernateUtil; public class Main { public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().openSession(); Book book = new Book(); book.setPageCount(520); book.setTitle("Tales of Round Table"); session.save(book);//сохранили книгу, наш id сгенерировался и сразу заполнился book = (Book) session.get(Book.class, book.getId()); book.setPageCount(430); session.save(book); session.delete(book); session.flush(); session.close(); } }
Можете расставить точки остановок и следить как изменяется содержание таблицы books с каждой строчкой.
В Hibernate есть следующие уровни кеша:
Session level (1 level cache) - работает только между открытием и закрытием сессии. Например, если вы выбрали объект, а затем хотите его еще раз выбрать - он будет браться из кеша. После закрытия сессии весь кеш очистится. Именно при закрытии сессии, т.к. сессия может быть использована на протяжении нескольких транзакций, то эти несколько транзакций могут использовать session level cache совместно. Однако как правило при работе с Hibernate - сессия живет одну транзакцию, а то и меньше.
Process level (2 level cache, SessionFactory cache) - здесь кешируются данные всех сессий. Нужно заметить однако, что если мы говорим про eager loading (lazy=false), то Hibernate никогда не будет искать дочерние объекты в кеше. Например,
from Authors a join a.books
Автора выберутся из кеша, а книги - из БД. Если Книги загружаются в ходе обращения к ним, то есть используя ленивую загрузку, тогда объекты возьмуться из кеша (если они там есть конечно же).
Query cache - это расширение кеша 2го уровня. Если его активировать, то при выполнении запроса, все ID выбранных объектов будут сохраняться в кеше. При повторном выполнении запроса, он не буден выполнен в БД, просто возьмуться все ID и соответствующие объекты выберуться из кеша 2го уровня. Полезен такой кеш только для read-only данных или данных, которые очень редко изменяются, т.к. этот кеш очищается полностью если произойдет хоть одно изменение в затронутой запросом таблице. Можно сделать так, чтоб по умолчанию кеш запросов был включен, для этого нужно установить в конфигурации Hibernate:
hibernate.cache.use_query_cache в true
Пример создание фабрики, Hibernate и Jpa:
File: Professor.java import javax.persistence.Entity; import javax.persistence.Id; @Entity public class Professor { @Id private int id; private String name; private long salary; public Professor() {} public Professor(int id) { this.id = id; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getSalary() { return salary; } public void setSalary(long salary) { this.salary = salary; } public String toString() { return "Professor id: " + getId() + " name: " + getName() + " salary: " + getSalary(); } } File: ProfessorService.java import java.util.Collection; import javax.persistence.EntityManager; import javax.persistence.Query; public class ProfessorService { protected EntityManager em; public ProfessorService(EntityManager em) { this.em = em; } public Professor createProfessor(int id, String name, long salary) { Professor emp = new Professor(id); emp.setName(name); emp.setSalary(salary); em.persist(emp); return emp; } public void removeProfessor(int id) { Professor emp = findProfessor(id); if (emp != null) { em.remove(emp); } } public Professor raiseProfessorSalary(int id, long raise) { Professor emp = em.find(Professor.class, id); if (emp != null) { emp.setSalary(emp.getSalary() + raise); } return emp; } public Professor findProfessor(int id) { return em.find(Professor.class, id); } public Collection<Professor> findAllProfessors() { Query query = em.createQuery("SELECT e FROM Professor e"); return (Collection<Professor>) query.getResultList(); } } File: JPAUtil.java import java.io.Reader; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; public class JPAUtil { Statement st; public JPAUtil() throws Exception{ Class.forName("org.hsqldb.jdbcDriver"); System.out.println("Driver Loaded."); String url = "jdbc:hsqldb:data/tutorial"; Connection conn = DriverManager.getConnection(url, "sa", ""); System.out.println("Got Connection."); st = conn.createStatement(); } public void executeSQLCommand(String sql) throws Exception { st.executeUpdate(sql); } public void checkData(String sql) throws Exception { ResultSet rs = st.executeQuery(sql); ResultSetMetaData metadata = rs.getMetaData(); for (int i = 0; i < metadata.getColumnCount(); i++) { System.out.print(" "+ metadata.getColumnLabel(i + 1)); } System.out.println(" ----------------------------------"); while (rs.next()) { for (int i = 0; i < metadata.getColumnCount(); i++) { Object value = rs.getObject(i + 1); if (value == null) { System.out.print(" "); } else { System.out.print(" "+value.toString().trim()); } } System.out.println(""); } } } File: Main.java import java.util.Collection; import java.util.Iterator; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public class Main { public static void main(String[] a) throws Exception { JPAUtil util = new JPAUtil(); EntityManagerFactory emf = Persistence.createEntityManagerFactory("ProfessorService"); EntityManager em = emf.createEntityManager(); ProfessorService service = new ProfessorService(em); em.getTransaction().begin(); Professor emp = service.createProfessor(1,"name", 100); emp = service.createProfessor(2,"name 2", 100); Collection emps = em.createQuery("SELECT e FROM Professor e").getResultList(); for (Iterator i = emps.iterator(); i.hasNext();) { Professor e = (Professor) i.next(); System.out.println("Professor " + e.getId() + ", " + e.getName()); } util.checkData("select * from Professor"); em.getTransaction().commit(); em.close(); emf.close(); } } File: persistence.xml <persistence xmlns="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 http://java.sun.com/xml/ns/persistence/persistence" version="1.0"> <persistence-unit name="JPAService" transaction-type="RESOURCE_LOCAL"> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.connection.driver_class" value="org.hsqldb.jdbcDriver"/> <property name="hibernate.connection.username" value="sa"/> <property name="hibernate.connection.password" value=""/> <property name="hibernate.connection.url" value="jdbc:hsqldb:data/tutorial"/> </properties> </persistence-unit> </persistence>
Видео - Java для тестировщиков:
В проекте: обычная лобби комната с кнопкой отправления запроса, исходники расширения для комнаты. (В комнате GameLobby/The Lobby в настройках extensions установить расширение TestExt и майн класс testext.TestExt) Для проверки создания таблицы используйте класс MyTestDb в пакете TestExt. При билде jar не забудте: 1) Включить файлы не только из папки extensions/_lib_ , но и из sfs2x/lib (slf4j-log4j12, slf4j-api-1.5.10, log4j-1.2.15), 2) Так же создать в папке sfs2x папку META-INF и поменять файл log4j.properties в sfs2x/log4j.properties |