You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cookbook.xml 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <?xml version="1.0"?>
  2. <document>
  3. <properties>
  4. <author email="jahlborn@users.sf.net">James Ahlborn</author>
  5. <title>Jackcess Cookbook</title>
  6. </properties>
  7. <body>
  8. <section name="Introduction">
  9. <p>
  10. This cookbook will attempt to familiarize the reader with the various
  11. nooks and crannies of the Jackcess 2.x API. The API is large due to
  12. the large feature-set that an Access Database provides, so this
  13. cookbook will by no means be exhaustive. However, this will hopefully
  14. give the reader enough useful building blocks such that the rest of
  15. the API can be discovered and utilized as necessary.
  16. </p>
  17. <p>
  18. This cookbook is a cross between a tutorial and a reference, so the
  19. reader should be able to skip to relevant sections without needing to
  20. read the entire preceding text.
  21. </p>
  22. <p>
  23. While this cookbook strives to present best practices for both the
  24. Jackcess API and Java programming in general, at times, the code may
  25. be trimmed for the sake of brevity. For the same reason, pseudo-code
  26. may be used in places where the actual code is not relevant to the
  27. example.
  28. </p>
  29. <macro name="toc">
  30. <param name="section" value="0"/>
  31. <param name="fromDepth" value="0"/>
  32. <param name="toDepth" value="4"/>
  33. </macro>
  34. </section>
  35. <section name="The Basics">
  36. <subsection name="Opening an existing Database">
  37. <p>
  38. So you have an Access Database and you want to do something with it.
  39. You want to use Java, and you may not even be running on Windows (or
  40. you tried the JDBC/ODBC bridge and it failed miserably). Through
  41. some Google-fu, you landed here at the Jackcess project. Now what?
  42. </p>
  43. <p>
  44. Well, the first thing you need to do is open the database. The
  45. entry point class in the Jackcess API is, surprisingly enough, the
  46. <a href="apidocs/com/healthmarketscience/jackcess/Database.html">Database</a> class.
  47. </p>
  48. <source>
  49. Database db = DatabaseBuilder.open(new File("mydb.mdb"));
  50. </source>
  51. <p>
  52. That's it, now you have a Database instance (maybe this isn't that
  53. difficult after all).
  54. </p>
  55. <p>
  56. Important note, <i>always</i> make sure you close a Database
  57. instance when you are finished with it (preferably in a finally
  58. block like any other important resource). Failure to close the
  59. Database instance could result in data loss or database corruption.
  60. </p>
  61. </subsection>
  62. <subsection name="Reading a Table">
  63. <p>
  64. Okay, so you have a Database instance, now what? Since pretty much
  65. everything in an Access database lives in a table, grabbing a <a href="apidocs/com/healthmarketscience/jackcess/Table.html">Table</a>
  66. would be the logical next step.
  67. </p>
  68. <source>
  69. Table table = db.getTable("Test");
  70. </source>
  71. <p>
  72. Where's the data? While a <a
  73. href="apidocs/com/healthmarketscience/jackcess/Cursor.html">Cursor</a>
  74. is the best way to interact with the data in a Table, for the sake
  75. of simplicity when just getting started, we will use the simplified
  76. iteration provided by the Table class itself. When reading row
  77. data, it is generally provided as a <a
  78. href="apidocs/com/healthmarketscience/jackcess/Row.html">Row</a> where the keys are the column
  79. names and the values are the strongly typed column values.
  80. </p>
  81. <source>
  82. for(Row row : table) {
  83. System.out.prinln("Look ma, a row: " + row);
  84. }
  85. </source>
  86. <p>
  87. So, what's in a row? Well, let's assume your "Test" table is
  88. defined in the following way in Access:
  89. </p>
  90. <div class="indented">
  91. <table border="1">
  92. <tr>
  93. <th>Field Name</th><th>Data Type</th>
  94. </tr>
  95. <tr>
  96. <td>ID</td><td>AutoNumber (Long Integer)</td>
  97. </tr>
  98. <tr>
  99. <td>Name</td><td>Text</td>
  100. </tr>
  101. <tr>
  102. <td>Salary</td><td>Currency</td>
  103. </tr>
  104. <tr>
  105. <td>StartDate</td><td>Date/Time</td>
  106. </tr>
  107. </table>
  108. </div>
  109. <p>
  110. Then, given a row of data, we could inspect the various <a href="apidocs/com/healthmarketscience/jackcess/Column.html">Columns</a> and
  111. their values like so:
  112. </p>
  113. <source>
  114. Row row = ...;
  115. for(Column column : table.getColumns()) {
  116. String columnName = column.getName();
  117. Object value = row.get(columnName);
  118. System.out.println("Column " + columnName + "(" + column.getType() + "): "
  119. + value + " (" + value.getClass() + ")");
  120. }
  121. // Example Output:
  122. //
  123. // Column ID(LONG): 27 (java.lang.Integer)
  124. // Column Name(TEXT): Bob Smith (java.lang.String)
  125. // Column Salary(MONEY): 50000.00 (java.math.BigDecimal)
  126. // Column StartDate(SHORT_DATE_TIME): Mon Jan 05 09:00:00 EDT 2010 (java.util.Date)
  127. </source>
  128. <p>
  129. As you can see in this example (and as previously mentioned), the
  130. row values are <i>strongly typed</i> Java objects. In Jackcess, the
  131. column types are represented by a Java enum named <a href="apidocs/com/healthmarketscience/jackcess/DataType.html">DataType</a>.
  132. The DataType javadoc details the Java types used to return row
  133. values as well as the value types which are acceptable inputs for
  134. new rows (more on this later). One other thing to note in this
  135. example is that the column names in the row Map are <i>case
  136. sensitive</i> strings (although other parts of the API strive to
  137. mimic Access's love of case-insensitivity).
  138. </p>
  139. </subsection>
  140. <subsection name="Adding a Row">
  141. <p>
  142. Awesome, so now we can read what's already there. Of course, lots
  143. of tools can do that. Now we want to write some data.
  144. </p>
  145. <p>
  146. The main hurdle to writing data is figuring out how to get the data
  147. in the right columns. The primary method for adding a row to a
  148. Table is the <a href="apidocs/com/healthmarketscience/jackcess/Table.html#addRow(java.lang.Object...)">addRow(Object...)</a>
  149. method. This method should be called with the appropriate, strongly
  150. typed Java object values <i>in the order of the Columns of the
  151. Table</i>. The order of the Columns on the Table instance <i>may
  152. not be the same as the display order of the columns in Access</i>.
  153. (Re-read those last two sentences again, as it will save you a lot of
  154. grief moving forward).
  155. </p>
  156. <p>
  157. Additionally, when adding rows, we never provide a value for any
  158. "auto" columns. You can provide a value (any value in fact), but it
  159. will be ignored (in the example below, we use a useful constant which
  160. makes the intent clear to any future developer).
  161. </p>
  162. <p>
  163. So, assuming that the order of the Columns on the Table instance is
  164. "ID", "Name", "Salary", and "StartDate", this is how we would add a
  165. row to the "Test" table:
  166. </p>
  167. <source>
  168. String name = "bob";
  169. BigDecimal salary = new BigDecimal("1000.00");
  170. Date startDate = new Date();
  171. table.addRow(Column.AUTO_NUMBER, name, salary, startDate);
  172. </source>
  173. <p>
  174. There you have it, a new row in your Access database.
  175. </p>
  176. </subsection>
  177. </section>
  178. <section name="Starting from Scratch">
  179. <subsection name="Creating a new Database">
  180. <p>
  181. While updating existing content is nice, and necessary, many times
  182. we want to create an entire new Database. While Jackcess doesn't
  183. support everything you may need when creating a new database, it
  184. does support a wide range of functionality, and adds more all the
  185. time. (If you started using Jackcess a while ago, you should
  186. definitely keep tabs on the <a href="changes-report.html">release notes</a>, as your knowledge of what
  187. is possible may be out of date).
  188. </p>
  189. <p>
  190. As of version 2.1.5, Jackcess supports:
  191. </p>
  192. <ul>
  193. <li>Creating databases for Access all versions 2000-2019</li>
  194. <li>Creating columns for all simple data types</li>
  195. <li>Creating tables with single-table Indexes</li>
  196. <li>Creating tables with (index backed) foreign-key constraints
  197. (i.e. relationships with integrity enforcement enabled)</li>
  198. </ul>
  199. <p>
  200. Some notable gaps:
  201. </p>
  202. <ul>
  203. <li>Cannot currently create tables with "complex" columns
  204. (attachment, multi-value, versioned memo)</li>
  205. </ul>
  206. <p>
  207. As long as your needs fall into the aforementioned constraints (or
  208. if you can fake it), then let's get started!
  209. </p>
  210. <p>
  211. The first thing we need to choose is the desired <a href="apidocs/com/healthmarketscience/jackcess/Database.FileFormat.html">FileFormat</a>
  212. of the new Database. Armed with that information, we can start
  213. putting the pieces together using the appropriate builder classes.
  214. Notice that the result of creating the new Database is an open
  215. Database instance.
  216. </p>
  217. <source>
  218. File file = new File("test.mdb");
  219. Database db = new DatabaseBuilder(file)
  220. .setFileFormat(Database.FileFormat.V2000)
  221. .create();
  222. </source>
  223. </subsection>
  224. <subsection name="Creating a Table">
  225. <p>
  226. An empty Database isn't very useful, of course, so we probably want
  227. to add a Table or two. The following code will create the table
  228. that we have used in the above examples. Notice that, like Database
  229. creation, the result of the Table creation is an open Table
  230. instance.
  231. </p>
  232. <source>
  233. Table table = new TableBuilder("Test")
  234. .addColumn(new ColumnBuilder("ID", DataType.LONG)
  235. .setAutoNumber(true))
  236. .addColumn(new ColumnBuilder("Name", DataType.TEXT))
  237. .addColumn(new ColumnBuilder("Salary", DataType.MONEY))
  238. .addColumn(new ColumnBuilder("StartDate", DataType.SHORT_DATE_TIME))
  239. .toTable(db);
  240. </source>
  241. <p>
  242. That is a very simple Table. In the real world, we often need Indexes
  243. to speed up lookups and enforce uniqueness constraints. Adding the
  244. following to the previous example will make the "ID" column a primary
  245. key and enable speedier lookups on the "Name" column.
  246. </p>
  247. <source>
  248. // ... new TableBuilder( ...
  249. .addIndex(new IndexBuilder(IndexBuilder.PRIMARY_KEY_NAME)
  250. .addColumns("ID").setPrimaryKey())
  251. .addIndex(new IndexBuilder("NameIndex")
  252. .addColumns("Name"))
  253. // ... .toTable( ...
  254. </source>
  255. <p>
  256. Don't forget to close the Database when you are finished building it
  257. and now you have a fresh, new Database on which to test some more
  258. recipes.
  259. </p>
  260. </subsection>
  261. </section>
  262. <section name="Finding Stuff">
  263. <subsection name="Cursors, what are they good for?">
  264. <p>
  265. Actually, a lot! Now that we've covered the basics, let's move into
  266. some intermediate level topics. The first and foremost is the use
  267. of the <a
  268. href="apidocs/com/healthmarketscience/jackcess/Cursor.html">Cursor</a>.
  269. As mentioned earlier, Cursors are the best way to interact with the
  270. data in a Table (beyond trivial data entry). If you are familiar
  271. with databases in general, then cursors should be a familiar idea.
  272. A Cursor is, in essence, the combination of a Table and a "bookmark"
  273. pointing to a Row of data in the Table. The various Cursor
  274. operations either move the bookmark around within the Table or
  275. provide read/write operations on the current Row. While caching and
  276. re-using Cursor instances will provide benefits with respect to both
  277. speed and memory, they are "lightweight" enough to be used in an
  278. on-demand capacity.
  279. </p>
  280. <p>
  281. The simplest case involves a normal, un-indexed cursor for a given
  282. table. The cursor will traverse the table in no particular row order
  283. but it can still be used to find rows where column(s) match
  284. specified values. For example...
  285. </p>
  286. <source>
  287. Table table = db.getTable("Test");
  288. Cursor cursor = CursorBuilder.createCursor(table);
  289. boolean found = cursor.findFirstRow(Collections.singletonMap("ID", 1));
  290. if (found) {
  291. System.out.println(String.format("Row found: Name = '%s'.",
  292. cursor.getCurrentRowValue(table.getColumn("Name"))));
  293. } else {
  294. System.out.println("No matching row was found.");
  295. }
  296. </source>
  297. <p>
  298. ...will search for the row where <code>"ID" == 1</code>. Since the
  299. cursor does not use an index it will perform the equivalent of a
  300. "table scan" while searching.
  301. </p>
  302. <p>
  303. Cursors can also use an existing index on the table to (1) control
  304. the order in which they traverse the table, and (2) find rows
  305. faster. Since we defined the "ID" column as our Primary Key we can
  306. also perform the above search like this...
  307. </p>
  308. <source>
  309. Table table = db.getTable("Test");
  310. IndexCursor cursor = CursorBuilder.createCursor(table.getPrimaryKeyIndex());
  311. boolean found = cursor.findFirstRow(Collections.singletonMap("ID", 1));
  312. // ... proceed as in previous example ...
  313. </source>
  314. <p>
  315. ...or by using the <code>CursorBuilder.findRowByPrimaryKey</code>
  316. "convenience method" like this:
  317. </p>
  318. <source>
  319. Table table = db.getTable("Test");
  320. Row row = CursorBuilder.findRowByPrimaryKey(table, 1);
  321. if (row != null) {
  322. System.out.println(String.format("Row found: Name = '%s'.",
  323. row.get("Name")));
  324. } else {
  325. System.out.println("No matching row was found.");
  326. }
  327. </source>
  328. <p>
  329. Either of the two previous approaches will use the Primary Key index
  330. to locate the row where <code>"ID" == 1</code>, potentially making
  331. it much faster to execute.
  332. </p>
  333. <p>
  334. As mentioned above, an index-backed cursor will also retrieve rows
  335. in index order, so if we wanted to retrieve all of the rows in
  336. alphabetical order by <code>"Name"</code> we could use the
  337. "NameIndex" index to create a cursor and then iterate through the
  338. rows like this:
  339. </p>
  340. <source>
  341. Table table = db.getTable("Test");
  342. IndexCursor cursor = CursorBuilder.createCursor(table.getIndex("NameIndex"));
  343. for(Row row : cursor) {
  344. System.out.println(String.format("ID=%d, Name='%s'.",
  345. row.get("ID"), row.get("Name")));
  346. }
  347. </source>
  348. <p>
  349. Or, if you wanted to iterate through all of the rows where
  350. <code>"Name" == 'bob'</code> you could do:
  351. </p>
  352. <source>
  353. Table table = db.getTable("Test");
  354. IndexCursor cursor = CursorBuilder.createCursor(table.getIndex("NameIndex"));
  355. for(Row row : cursor.newEntryIterable("bob")) {
  356. System.out.println(String.format("ID=%d, Name='%s'.", row.get("ID"), row.get("Name")));
  357. }
  358. </source>
  359. </subsection>
  360. </section>
  361. <!-- <section name="Miscellaneous Examples"> -->
  362. <!-- <p> -->
  363. <!-- FIXME, writeme -->
  364. <!-- </p> -->
  365. <!-- </section> -->
  366. </body>
  367. </document>